Secure every Partner API request by signing it from your backend with your API secret. Never expose the secret in frontend code, mobile apps, or browsers—store it server-side only (environment variables or a secrets vault).
The examples on this page use placeholders and env var names only. Your real sk_* secret must never be committed to source control or pasted into support tickets.
Each request sends headers proving you hold the secret without sending the secret over the wire. The server rebuilds the same string and compares HMAC-SHA256 signatures using timing-safe verification.
X-NameAI-Key-IdYesPublic credential key id (pk_sandbox_… or pk_live_…).X-NameAI-TimestampYesUnix timestamp in seconds UTC (not milliseconds).X-NameAI-NonceYesUnique value per request (UUID recommended). Reusing a nonce too soon is rejected.X-NameAI-SignatureYesHMAC-SHA256 over the canonical string, hex-encoded, prefixed with v1=.Idempotency-KeyPOST /ordersRequired on POST /api/partner/v1/orders so retries are safe. Same key + same body fingerprint replays the stored response.Send the hex digest with the version prefix:
X-NameAI-Signature: v1=<hex_lowercase>
Example:
X-NameAI-Signature: v1=9a4f1a2d7c8e0f1a2b3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f708192
Concatenate exactly six lines separated by newline (\n). Line order and spelling matter.
METHOD
PATH
QUERY_STRING
TIMESTAMP
NONCE
BODY_SHA256_HEXGET, POST, …)./, must match the request URL path (e.g. /api/partner/v1/domains/feed).X-NameAI-Timestamp.X-NameAI-Nonce.e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.Split parameters, sort by key ascending; if keys tie, sort by value. URL-encode keys and values per Name.ai's encoder (aligned with lib/server/partner-api/hmac.js), then join with &.
Example: ?expand=items&limit=10 becomes canonical pairs sorted — expand=items&limit=10 (order follows sort rules).
Request: POST /api/partner/v1/orders with JSON body {"domain":"…","expected_price":9900,…} — hash the exact bytes sent on the wire (same spacing, same UTF-8).
POST
/api/partner/v1/orders
1714309200
550e8400-e29b-41d4-a716-446655440000
<sha256_hex_of_exact_body>signature_hex = HMAC_SHA256(api_secret, canonical_string) → hex lowercase → header v1=+ hex.
Mirrors production helpers buildCanonicalString / signCanonicalString. Install: Node 18+ with native fetch / crypto.
import crypto from "crypto";
const EMPTY_SHA256 = crypto.createHash("sha256").update("").digest("hex");
function canonicalizeQuery(searchParams) {
const params =
searchParams instanceof URLSearchParams
? Array.from(searchParams.entries())
: Array.from(new URLSearchParams(searchParams || "").entries());
return params
.sort(([aKey, aValue], [bKey, bValue]) => {
if (aKey === bKey) return aValue.localeCompare(bValue);
return aKey.localeCompare(bKey);
})
.map(([key, value]) => {
const enc = (s) =>
encodeURIComponent(s).replace(/[!'()*]/g, (ch) =>
"%" + ch.charCodeAt(0).toString(16).toUpperCase()
);
return enc(key) + "=" + enc(value);
})
.join("&");
}
function bodySha256(bodyString) {
if (bodyString == null || bodyString === "") return EMPTY_SHA256;
return crypto.createHash("sha256").update(bodyString, "utf8").digest("hex");
}
function buildCanonicalString({ method, pathname, searchParams, timestamp, nonce, bodyHash }) {
return [
String(method || "").toUpperCase(),
pathname || "/",
canonicalizeQuery(searchParams),
String(timestamp || ""),
String(nonce || ""),
bodyHash || EMPTY_SHA256,
].join('\n');
}
export function signPartnerRequest({ secret, method, url, bodyString, timestamp, nonce }) {
const u = new URL(url);
const bodyHash = bodySha256(bodyString);
const canonical = buildCanonicalString({
method,
pathname: u.pathname,
searchParams: u.searchParams,
timestamp,
nonce,
bodyHash,
});
const sig = crypto.createHmac("sha256", secret).update(canonical, "utf8").digest("hex");
return { canonical, signatureHeader: "v1=" + sig };
}
// Example GET
const secret = process.env.NAMEAI_API_SECRET; // sk_sandbox_… or sk_live_…
const keyId = process.env.NAMEAI_KEY_ID; // pk_sandbox_…
const base = "https://your-domain.com/api/partner/v1/domains/feed?limit=10";
const ts = String(Math.floor(Date.now() / 1000));
const nonce = crypto.randomUUID();
const { signatureHeader } = signPartnerRequest({
secret,
method: "GET",
url: base,
bodyString: "",
timestamp: ts,
nonce,
});
const res = await fetch(base, {
headers: {
"X-NameAI-Key-Id": keyId,
"X-NameAI-Timestamp": ts,
"X-NameAI-Nonce": nonce,
"X-NameAI-Signature": signatureHeader,
},
});
console.log(await res.json());
// Example POST — create order (hash = exact body bytes you send; reuse same idempotency key only when retrying the same op)
const orderUrl = "https://your-domain.com/api/partner/v1/orders";
const idempotencyKey = "ord_" + crypto.randomUUID();
const bodyString = JSON.stringify({
domain: "example.com",
expected_price: 9900,
currency: "USD",
buyer: { email: "[email protected]", name: "Ada", country: "US" },
return_url: "https://your-store.com/done",
cancel_url: "https://your-store.com/cancel",
});
const tsPost = String(Math.floor(Date.now() / 1000));
const noncePost = crypto.randomUUID();
const postSigned = signPartnerRequest({
secret,
method: "POST",
url: orderUrl,
bodyString,
timestamp: tsPost,
nonce: noncePost,
});
const orderRes = await fetch(orderUrl, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-NameAI-Key-Id": keyId,
"X-NameAI-Timestamp": tsPost,
"X-NameAI-Nonce": noncePost,
"X-NameAI-Signature": postSigned.signatureHeader,
"Idempotency-Key": idempotencyKey,
},
body: bodyString,
});
console.log(await orderRes.json());Same algorithm: build the canonical string exactly, then HMAC-SHA256 with the raw secret string as key (UTF-8).
import hashlib
import hmac
import json
import time
import uuid
from urllib.parse import urlparse, parse_qsl
EMPTY_SHA256 = hashlib.sha256(b"").hexdigest()
def encode_component(s: str) -> str:
"""Match JS encodeURIComponent + /[!'()*]/g — see partner-api/hmac.js encodeQueryComponent."""
from urllib.parse import quote
out = quote(s, safe="-_.!~*'()")
for ch in "!'*()":
out = out.replace(ch, f"%{ord(ch):02X}")
return out
def canonicalize_query(query: str) -> str:
if not query:
return ""
qs = query.lstrip("?")
pairs = parse_qsl(qs, keep_blank_values=True)
pairs.sort(key=lambda kv: (kv[0], kv[1]))
return "&".join(f"{encode_component(k)}={encode_component(v)}" for k, v in pairs)
def body_sha256(body: bytes | str | None) -> str:
if body is None or body == b"" or body == "":
return EMPTY_SHA256
if isinstance(body, str):
body = body.encode("utf8")
return hashlib.sha256(body).hexdigest()
def build_canonical(method: str, url: str, body_for_hash: bytes | str | None, timestamp: str, nonce: str) -> str:
u = urlparse(url)
path = u.path or "/"
q = canonicalize_query(u.query)
bh = body_sha256(body_for_hash)
lines = [method.upper(), path, q, timestamp, nonce, bh]
return "\n".join(lines)
def sign(secret: str, canonical: str) -> str:
return hmac.new(secret.encode("utf8"), canonical.encode("utf8"), hashlib.sha256).hexdigest()
# Example GET
import os
import urllib.request
secret = os.environ["NAMEAI_API_SECRET"]
key_id = os.environ["NAMEAI_KEY_ID"]
full_url = "https://your-domain.com/api/partner/v1/domains/feed?limit=10"
ts = str(int(time.time()))
nonce = str(uuid.uuid4())
canon = build_canonical("GET", full_url, "", ts, nonce)
sig = f"v1={sign(secret, canon)}"
req = urllib.request.Request(
full_url,
headers={
"X-NameAI-Key-Id": key_id,
"X-NameAI-Timestamp": ts,
"X-NameAI-Nonce": nonce,
"X-NameAI-Signature": sig,
},
method="GET",
)
with urllib.request.urlopen(req) as resp:
print(resp.read().decode())
# Example POST — create order (body bytes for signing must match request body exactly)
order_url = "https://your-domain.com/api/partner/v1/orders"
body_str = json.dumps(
{
"domain": "example.com",
"expected_price": 9900,
"currency": "USD",
"buyer": {"email": "[email protected]", "name": "Ada", "country": "US"},
"return_url": "https://your-store.com/done",
"cancel_url": "https://your-store.com/cancel",
},
separators=(",", ":"),
)
idempotency_key = "ord_" + str(uuid.uuid4())
ts_post = str(int(time.time()))
nonce_post = str(uuid.uuid4())
canon_post = build_canonical("POST", order_url, body_str, ts_post, nonce_post)
sig_post = f"v1={sign(secret, canon_post)}"
post_req = urllib.request.Request(
order_url,
data=body_str.encode("utf8"),
headers={
"Content-Type": "application/json",
"X-NameAI-Key-Id": key_id,
"X-NameAI-Timestamp": ts_post,
"X-NameAI-Nonce": nonce_post,
"X-NameAI-Signature": sig_post,
"Idempotency-Key": idempotency_key,
},
method="POST",
)
with urllib.request.urlopen(post_req) as resp:
print(resp.read().decode())Compute Signature with your script first; substitute placeholders. Include Idempotency-Key for order creation.
curl -X POST "https://your-domain.com/api/partner/v1/orders" \
-H "Content-Type: application/json" \
-H "X-NameAI-Key-Id: pk_live_xxx" \
-H "X-NameAI-Timestamp: 1714309200" \
-H "X-NameAI-Nonce: 550e8400-e29b-41d4-a716-446655440000" \
-H "X-NameAI-Signature: v1=<hex_from_canonical>" \
-H "Idempotency-Key: ord_001" \
-d '{"domain":"example.com","expected_price":9900,"currency":"USD","buyer":{"email":"[email protected]"},"return_url":"https://your-store.com/done","cancel_url":"https://your-store.com/cancel"}'invalid_signatureWrong secret, wrong canonical string, or body changed after signing.invalid_timestampNot Unix seconds, clock drift beyond 5 minutes, or malformed.replay_detectedNonce reused too soon.missing_signature_headersMissing one of the four X-NameAI-* auth headers.missing_idempotency_keyPOST /orders without Idempotency-Key.pk_…) instead of the secret (sk_…)./orders but requesting /api/partner/v1/orders.sk_* in vault / env; rotate if leaked.Related: API surface, Sandbox & live.