Authentication
Every /v1 commerce request except /v1/capabilities,
/v1/agent-manifest.json, /v1/sim-interest, /healthz and the Stripe receiver
requires an HMAC-SHA256 signature. There is no bearer token mode. The API ID identifies the key;
the secret signs the request.
Credentials
| Value | Example shape | Where it goes |
|---|---|---|
| API ID | rw_sandbox_xxxxxxxxxxxx | X-Partner-Key-Id header |
| Secret | Shown once at key creation | Never transmitted; used to compute the HMAC |
| Environment | sandbox or live | Inside the canonical string |
Store the secret in a secret manager on the server that makes the call. Do not put it in a browser, a mobile app, a repository, a URL, a log, a screenshot or a prompt. The dashboard only shows it at creation. If you lose it, rotate the key.
Required headers
Code
Idempotency-Key is required on POST /v1/orders, POST /v1/orders/{id}/redeem
and POST /v1/wallet/funding. If you send it on another request, it is included in the
canonical string but does not create deduplication behaviour.
Canonical string
Join these ten fields with a single LF (\n) and no trailing LF:
Code
Then compute HMAC-SHA256(secret, canonical) and send the result as lowercase hex in
X-Partner-Signature.
Rules that matter in practice:
- Authority is the host only, lowercase and without a port. A call to
https://roamworks.tech/v1/quotessignsroamworks.tech; a sandbox call tohttps://sandbox.roamworks.tech/v1/quotessignssandbox.roamworks.tech. - Path and query must match what you send, byte for byte. Do not reorder, decode or re-encode query parameters after signing.
- Method is uppercase in the canonical string.
- Body hash is over the exact raw bytes sent. Serialize JSON once and sign that string.
- Empty body hashes the zero-length string:
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855. - Timestamp is Unix seconds. The server accepts a maximum skew of five minutes.
- Nonce must be unique per key for ten minutes. A valid replay returns
401 replayed. - Idempotency key is included even when empty. Use the same value you send in the header.
Node.js helper
Code
Python helper
Code
Scopes
A key carries a set of scopes. A missing scope returns 403 forbidden after the signature is
valid. The current handler mapping is:
| Endpoint | Required scope |
|---|---|
GET /v1/catalog | catalog:read |
POST /v1/quotes | catalog:read (current) / quotes:write (target) |
GET /v1/quotes?id= (preview) | catalog:read |
POST /v1/orders | orders:write |
GET /v1/orders | orders:read |
GET /v1/orders/{id} | orders:read |
POST /v1/orders/{id}/redeem | esims:redeem |
GET /v1/wallet | wallet:read |
POST /v1/wallet/funding | wallet:fund |
GET /v1/agents/me | catalog:read |
GET /v1/usage, GET /v1/activity | catalog:read |
| Agent management paths | agents:manage |
The published contract assigns quotes:write to quote creation, but the running handler
currently checks catalog:read. During rollout, give a key both scopes if it needs quotes.
esims:read is an assignable agent scope with no required handler yet. agents:manage
is a principal scope: an agent key is always rejected from agent-management paths. See
Agent identities.
Key and secret lifecycle
- Create: the dashboard or
POST /v1/agents/{agentId}/keysreturns the API ID and secret once. Copy the secret into the server secret manager immediately. - Rotate:
POST /v1/agents/{agentId}/keys/{keyId}/rotateissues a new key and sets the old key to expire after 24 hours. - Expire: keys can have an expiry. Agent keys inherit the agent expiry or default to 90 days.
- Revoke:
DELETE /v1/agents/{agentId}revokes the agent and its keys. A revoked key returns401 unauthorized. - Scope changes: take effect on the next request. There is no token cache to invalidate.
Clock, nonce and replay
Use a synchronised clock. If the host clock drifts more than five minutes, every request fails with
401 request_expired. Generate a fresh nonce for every attempt, including retries of the same
idempotent request. A retry may reuse the idempotency key, but never the nonce.
Common signing failures
| Response | Meaning | Fix |
|---|---|---|
401 missing_signature | One or more signing headers absent | Send all four signing headers. |
401 signature_invalid | HMAC mismatch | Check secret, environment, authority, raw path and body hash. |
401 request_expired | Timestamp outside the window | Sync time and retry with a fresh timestamp and nonce. |
401 replayed | Nonce reused | Generate a new nonce. Do not reuse the whole header set. |
401 key_expired | Key passed its expiry | Rotate or issue a new key. |
401 agent_inactive | Agent is suspended, revoked or expired | Ask the partner owner to reactivate or reissue. |
403 ip_not_allowed | Source IP outside the key allowlist | Use an allowed egress address. |
403 forbidden | Key lacks the endpoint scope | Ask the owner to grant the scope. |
Transport and storage
- TLS 1.2 or newer. Certificate validation is mandatory; do not disable it.
- The API sets
Cache-Control: private, no-storeon/v1responses. - Secrets are encrypted at rest with key-version metadata. Only the API can decrypt them.
- Do not copy a secret into a support ticket. Share the API ID and request ID instead.
For request-level failures, see Errors.