Authentication
HMAC-SHA256 signing rules, nonce-based replay protection, and the three credentials you receive at onboarding.
Every authenticated request carries three headers: X-API-KEY, X-API-SIGN, X-API-NONCE. Sign the exact bytes you send — that’s the whole rule. Same in bash, Python, Node; see the worked example below, or jump to the signing template.
Required headers
| Header | Required | Description |
|---|---|---|
| X-API-KEY | Yes | Public partner key. May carry an optional code segment: apiKey.codeName— see /api/v1/codes. |
| X-API-SIGN | Yes | Hex signature keyed by your apiSecret, over the raw body — or, with X-API-TIMESTAMP, over `${timestamp}\n${nonce}\n${body}` (recommended). See the worked example below. |
| X-API-NONCE | Yes | Unique per request (UUID or 16-byte hex). Reusing a nonce returns code 3 AUTH_INVALID. |
| X-API-TIMESTAMP | Recommended | Optional, recommended. Unix seconds; must be recent. When sent, X-API-SIGNcovers the timestamp and nonce too. Requests without it use body-only signing and keep working — no deadline. |
Signing rules
- Sign the exact bytes you put on the wire.
- For
GET /api/v1/codesthe request has no body. Sign the empty string"": same HMAC primitive, zero input bytes.DELETE /api/v1/codescarries a JSON body{"code":"<code>"}. Sign that exact body, not the empty string. - Bad signature, unknown key, replayed nonce, and malformed envelope all collapse to the same response:
{ code: 3, msg: "AUTH_INVALID" }. You learn whether you got in, never which check failed.
Replay protection
Nonces are tracked server-side. Use a fresh random value per request, 16 to 64 characters. A UUID without hyphens is 32; with hyphens, 36; 64-char hex is also valid. Reuse, under-16, or over-64 all return { code: 3, msg: "AUTH_INVALID" }.
Recommended: also send X-API-TIMESTAMP (Unix seconds, must be recent) and sign `${timestamp}\n${nonce}\n${body}` instead of the body alone. Body-only signing stays supported, so there is no deadline. Copy-paste snippets are on the Signing template page.
Unauthenticated endpoints
/api/v1/currencies, /api/v1/pairs, and /api/v1/price accept unsigned requests for catalog and un-attributed quoting. Unsigned traffic never charges a partner budget, but each of the three read endpoints carries its own per-IP anonymous rate limit (independent buckets). Signed /api/v1/price calls receive partner-specific markup. Public limits are on the Rate limits page.
End-to-end example
POST /api/v1/price in three languages. The signature is computed over the exact JSON body bytes you send.
# Request a quote for 0.01 BTC -> USDT (Tron) with a 0.5% partner markup.
APIKEY="rWqZ...your-api-key...Rg"
APISECRET="G1JV...your-api-secret...n4"
NONCE=$(openssl rand -hex 16)
BODY='{"type":"float","fromCcy":"btc","toCcy":"usdt_trc20","direction":"from","amount":"0.01","afftax":50}'
SIGN=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$APISECRET" | sed 's/^.* //')
curl -sS -X POST "https://0trace.io/api/v1/price" \
-H "Content-Type: application/json" \
-H "X-API-KEY: $APIKEY" \
-H "X-API-SIGN: $SIGN" \
-H "X-API-NONCE: $NONCE" \
--data "$BODY"Common mistakes
- Re-serialising the body before sending. If your HTTP client pretty-prints or sorts keys before transmission, the body on the wire differs from the bytes you signed. Always sign the exact bytes you POST.
- Mixing keys across requests. The
X-API-KEYheader and the secret you HMAC with are a pair, issued together at onboarding. The header itself is not part of the HMAC input; only the raw request body is. Use theapiSecretthat was provisioned alongside theapiKeyyou send. - Stale, short, or oversized nonces. Generate a fresh random nonce per request, 16–64 characters inclusive. Reusing a value, sending an empty or under-16-character nonce, or sending an over-64-character nonce all return the same
X-API-NONCEAUTH_INVALID envelope.
Credential provisioning
You get three secrets, and they are always available in your cabinet — reveal them whenever you need:
{
"apiKey": "rWqZ...your-public-key...Rg",
"apiSecret": "G1JV...your-signing-secret...n4",
"webhookSecret": "Lh2P...your-webhook-secret...8K"
}Need a fresh secret? Contact us and we'll issue a new one — the previous value is invalidated immediately on our side.