Authentication
Sign every request with the current API key flow and EXN-* headers.
How signed requests work
The client signs request metadata with an Ed25519 private key. The server validates the API key, timestamp, signed payload fields, path and query, body hash, signature, and access scope.
For API key creation options and the public key registration format, see API key creation options.
Signed request headers
| Header | Required | Purpose |
|---|---|---|
EXN-API-KEY | Yes | Public API key identifier. Must match api_key inside EXN-DATA. |
EXN-IDEMPOTENCY-KEY | Yes | Empty for signed GET requests; non-empty for mutating requests. |
EXN-TIMESTAMP | Yes | Unix timestamp in milliseconds. Must match timestamp inside EXN-DATA. |
EXN-SIGN-VERSION | Yes | Signature protocol version. Current value: 1. |
EXN-DATA | Yes | Base64url-encoded signed payload without = padding. |
EXN-SIGN | Yes | Ed25519 signature of the decoded EXN-DATA payload bytes, base64url-encoded without = padding. |
Decoded EXN-DATA payload
| Field | Description |
|---|---|
api_key | Same value as EXN-API-KEY. |
idempotency_key | Same value as EXN-IDEMPOTENCY-KEY; empty string for signed GET requests. |
timestamp | Same value as EXN-TIMESTAMP, in Unix milliseconds. |
sign_version | Same value as EXN-SIGN-VERSION. |
method | Uppercase HTTP method, for example GET or POST. |
path | Request path with query string exactly as sent by the client. |
body_hash | SHA-256 hash of the exact request body, base64url-encoded without padding. |
Signing rules
EXN-SIGNsigns the decodedEXN-DATApayload bytes, not the base64url string.EXN-DATAandEXN-SIGNmust use base64url without=padding.- For requests without a body,
body_hashis the SHA-256 hash of the empty byte sequence:47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU. - Query strings must be signed exactly as transmitted. Do not reorder, normalize, or re-encode query parameters after signing.
- Duplicate query parameters are rejected.
- For signed GET requests,
EXN-IDEMPOTENCY-KEYmust be present with an empty value. - For mutating requests,
EXN-IDEMPOTENCY-KEYmust be non-empty. - Generate a fresh timestamp and signature for every request.
Example
Use the complete read-only example to import your private key, discover your Access Point and send a signed request. The curl below shows the request shape only; generate fresh signed headers before sending it. Replace the host with the access_point returned by Access Point / Host Discovery. The semicolon sends the required empty header in curl.
curl -X GET "https://ap-4decef67.exness.com/v1/configuration/accounts/123456/account" \
-H "EXN-API-KEY: exnsk-{your_public_api_key}" \
-H "EXN-IDEMPOTENCY-KEY;" \
-H "EXN-TIMESTAMP: 1773656070123" \
-H "EXN-SIGN-VERSION: 1" \
-H "EXN-DATA: <base64url_encoded_payload>" \
-H "EXN-SIGN: <base64url_encoded_signature>"
{
"api_key": "exnsk-{your_public_api_key}",
"idempotency_key": "",
"timestamp": 1773656070123,
"sign_version": 1,
"method": "GET",
"path": "/v1/configuration/accounts/123456/account",
"body_hash": "47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU"
}
Common auth errors
| Code | error_message | Client action |
|---|---|---|
| 1000 | AUTH_INVALID_API_KEY | Check EXN-API-KEY, key expiry, and api_key inside EXN-DATA. |
| 1001 | AUTH_INVALID_SIGNATURE | Check EXN-DATA, EXN-SIGN, private key, payload serialization, body hash, timestamp, and base64url encoding. |
| 1002 | AUTH_PERMISSION_DENIED | Check account access, source IP restrictions, and API key permissions. |
Continue your journey
Now that you know how signing works, put it into practice by sending your first request to the API you are integrating with.