Keys
Each partner account has separate keys for the sandbox and for live. You create and revoke them in the developer dashboard at developers.aurages.com Coming soon.
| Item | Format | Use |
|---|---|---|
| Key id | pk_test_xxx / pk_live_xxx |
sent with every request; not a secret |
| Key secret | sk_test_xxx / sk_live_xxx |
signs requests; shown once at creation |
| Webhook secret | whsec_xxx |
verifies the events we send you |
- A test key works only on the sandbox address, a live key only on the live address.
- Live keys are issued after certification.
- The secret belongs on your server only. Never in a mobile app, a browser or a log.
| Environment | Address |
|---|---|
| Sandbox | https://sandbox.partners.aurmap.com/v1 (live now) |
| Live | https://partners.aurmap.com/v1 (opens for each developer once approved) |
The sandbox address works now with sandbox keys. The live address opens for each developer once their integration is reviewed and approved.
Headers
Every request carries:
| Header | Value |
|---|---|
X-AurMap-Key-Id |
the key id |
X-AurMap-Timestamp |
current unix time in seconds |
X-AurMap-Nonce |
16–128 chars of [A-Za-z0-9_-], new for every request |
X-AurMap-Signature |
the signature, lowercase hex |
Idempotency-Key |
required on every POST, PUT, PATCH and DELETE. See Errors |
How to sign
Build a five-line string, separated by \n:
METHOD
PATH
TIMESTAMP
NONCE
hex(SHA256(raw_body))Then compute HMAC-SHA256 of that string with your secret, as hex.
METHODin upper case:GET,POST,PUT,PATCHorDELETE.PATHis everything after the host exactly as you send it, including/v1and the query string. Example:/v1/orders?status=received&limit=50.raw_bodyis the exact bytes you send. Sign the string you send, not an object you re-encode after signing.- A request with no body signs the empty string, whose hash is
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855.
Test vector
| Secret | sk_test_example_secret_do_not_use |
| Method | PATCH |
| Path | /v1/places/plc_9q3w7e/menu/state |
| Timestamp | 1760443200 |
| Nonce | n0nce_Example_1234567890 |
| Body | {"as_of":"2026-10-14T18:00:00Z","sold_out":{"items":["kunafa"],"sizes":[],"options":[]}} |
| Body hash | 087c75de7bec170df0facfe62fba13182441513acb4f7fc4ad1289eca35d4971 |
| Signature | ec96466e5cc1ab8e845d984b3cec264483e38357fb849a3c81e4e26f74abb3c8 |
If your code produces the same signature, it is correct.
Node
import crypto from 'node:crypto';
export function sign(secret, method, path, ts, nonce, body) {
const bodyHash = crypto.createHash('sha256').update(body).digest('hex');
const canonical = [method.toUpperCase(), path, ts, nonce, bodyHash].join('\n');
return crypto.createHmac('sha256', secret).update(canonical).digest('hex');
}
// the test vector above
console.log(sign(
'sk_test_example_secret_do_not_use', 'PATCH',
'/v1/places/plc_9q3w7e/menu/state', '1760443200',
'n0nce_Example_1234567890', '{"as_of":"2026-10-14T18:00:00Z","sold_out":{"items":["kunafa"],"sizes":[],"options":[]}}'
)); // ec96466e...b3c8PHP
<?php
function aurmap_sign(string $secret, string $method, string $path, string $ts, string $nonce, string $body): string {
$canonical = implode("\n", [strtoupper($method), $path, $ts, $nonce, hash('sha256', $body)]);
return hash_hmac('sha256', $canonical, $secret);
}
echo aurmap_sign(
'sk_test_example_secret_do_not_use', 'PATCH',
'/v1/places/plc_9q3w7e/menu/state', '1760443200',
'n0nce_Example_1234567890', '{"as_of":"2026-10-14T18:00:00Z","sold_out":{"items":["kunafa"],"sizes":[],"options":[]}}'
); // ec96466e...b3c8curl
BODY='{"as_of":"2026-10-14T18:00:00Z","sold_out":{"items":["kunafa"],"sizes":[],"options":[]}}'
PATH_Q='/v1/places/plc_9q3w7e/menu/state'
TS=$(date +%s)
NONCE=$(openssl rand -hex 16)
BH=$(printf '%s' "$BODY" | openssl dgst -sha256 -hex | sed 's/^.* //')
SIG=$(printf '%s\n%s\n%s\n%s\n%s' PATCH "$PATH_Q" "$TS" "$NONCE" "$BH" \
| openssl dgst -sha256 -hmac "$AURMAP_KEY_SECRET" -hex | sed 's/^.* //')
curl -sS -X PATCH "$AURMAP_HOST$PATH_Q" \
-H "Content-Type: application/json" \
-H "X-AurMap-Key-Id: $AURMAP_KEY_ID" \
-H "X-AurMap-Timestamp: $TS" \
-H "X-AurMap-Nonce: $NONCE" \
-H "X-AurMap-Signature: $SIG" \
-H "Idempotency-Key: state-kunafa-0001" \
--data-raw "$BODY"The quickstart has a ready aurmap function that does all of this; the other pages use it.
Clock skew
- We accept a request when
X-AurMap-Timestampis within 300 seconds of our time, either way. - Outside that we answer
401 TIMESTAMP_SKEW. - Every response carries a
Dateheader. Compare it with your clock if the error repeats, and run NTP on your servers.
Nonces
- Each nonce is accepted once within 10 minutes per key. Reuse answers
401 NONCE_REUSED. - A badly signed request does not spend the nonce, so nobody can burn yours.
- If we answer
503 UNAVAILABLE, retry with a new nonce.
Retries
A retry is a new request: new timestamp, new nonce, new signature. But it carries the same Idempotency-Key. That way a command never runs twice.
Rotating keys
- Create a new key in the dashboard. Both keys work at the same time.
- Deploy the new key to your servers.
- Watch “last used” for the old key in the dashboard. When it stops, revoke it.
- Revocation is immediate. If you think a secret leaked, revoke it at once and create another.
Idempotency keys and rate limits belong to your account and environment, not to the key. Rotating never re-runs a command or changes your limits.
Webhook secrets rotate the same way. During the overlap we send two signatures in the same header; accept the message if either matches.
Signing errors
| Code | Cause |
|---|---|
KEY_UNKNOWN |
the key id does not exist |
KEY_REVOKED |
the key was revoked |
KEY_MODE_MISMATCH |
a test key on the live address, or the reverse |
TIMESTAMP_SKEW |
the time is more than 300 seconds from ours |
NONCE_INVALID |
the nonce has the wrong length or characters |
NONCE_REUSED |
the nonce was used before |
SIGNATURE_INVALID |
the signature does not match |
All answer 401. The most common cause of SIGNATURE_INVALID: signing the path without its query string, or re-encoding the JSON after signing.