Authentication
The ExaRoutes API uses a pair of headers, client-id and
api-key, to authenticate requests. Both must be sent on every
call.
Generating a key
- Sign in to the dashboard.
- Open Settings → API keys.
- Click Create key and give it a name (e.g. "Production server" or "Zapier").
- The response shows the
client-id+api-keyexactly once. Copy both before closing the dialog.
If you lose a key, rotate it: revoke the old one and create a new one. The old key stops working immediately on revoke. Compromised keys should be rotated, not just renamed. There is no secret-content rotation, only revoke + reissue.
Sending requests
Every authenticated request needs both headers:
POST /api/qr/generate HTTP/1.1
Host: api.exaroutes.com
Content-Type: application/json
client-id: cust_abc123…
api-key: sk_live_xyz789… cURL
curl https://api.exaroutes.com/api/qr/generate \
-H "client-id: cust_abc123…" \
-H "api-key: sk_live_xyz789…" \
-H "Content-Type: application/json" \
-d '{"qrType":"dynamic","dataType":"url","data":"https://example.com"}' Node.js (fetch)
const res = await fetch('https://api.exaroutes.com/api/qr/generate', {
method: 'POST',
headers: {
'client-id': process.env.EXAROUTES_CLIENT_ID,
'api-key': process.env.EXAROUTES_API_KEY,
'content-type': 'application/json',
},
body: JSON.stringify({
qrType: 'dynamic',
dataType: 'url',
data: 'https://example.com',
}),
})
const qr = await res.json() Python (requests)
import os, requests
resp = requests.post(
'https://api.exaroutes.com/api/qr/generate',
headers={
'client-id': os.environ['EXAROUTES_CLIENT_ID'],
'api-key': os.environ['EXAROUTES_API_KEY'],
},
json={
'qrType': 'dynamic',
'dataType': 'url',
'data': 'https://example.com',
},
)
resp.raise_for_status()
qr = resp.json() Targeting a workspace
If your account has multiple workspaces, the API picks your default
workspace unless you tell it otherwise. To target a specific
workspace, include an X-Workspace-Id header. The calling user
must be a member of that workspace or the request fails with 403 Not a member of the requested workspace.
X-Workspace-Id: ws_team_marketing | Header | Type | Required? | Notes |
|---|---|---|---|
client-id | string | yes | The customer-id half of your API credential. |
api-key | string | yes | The secret half. Treat like a password. |
X-Workspace-Id | string | optional | Override the default workspace. Caller must be a member. |
Content-Type | string | conditional | application/json on POST/PUT requests with a body. |
Step-up MFA
A small set of high-impact endpoints require fresh proof of identity even
after authentication: updating a dynamic QR's destination
(PUT /api/qr/dynamic-qr) and bulk-replacing
URLs across the workspace (POST /api/qr/workspace-bulk-replace)
are gated behind a 5-minute step-up grace. The first call returns
403 with { "error": "step_up_required", "tier": "standard" }.
Re-authenticate via the dashboard's MFA / password prompt, then subsequent
calls within the 5-minute window proceed. Step-up is dashboard-flow only.
Pure API-key automation that needs to mutate destinations should rotate
its keys as needed and stage destination edits via the dashboard or a
delete-then-create pattern.
Errors
| Status | Cause |
|---|---|
400 | Missing client-id or api-key header. |
401 | Invalid credentials (key revoked, wrong env, or key/client-id pair don't match). |
403 | Key valid but doesn't grant access to the requested resource (e.g. cross-workspace target). |
404 | API key not found (typo in the header value). |
Security
- API keys grant the same permissions as the user who created them. Treat them like passwords.
- Keys travel over TLS only. Plaintext HTTP requests are rejected at the load balancer.
- Store keys in your secret manager (AWS Secrets Manager, Doppler, 1Password CLI, etc.). Never commit them.
- If you suspect a key is compromised, revoke it from Settings → API keys immediately.