Skip to main content
ER ExaRoutes

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

  1. Sign in to the dashboard.
  2. Open Settings → API keys.
  3. Click Create key and give it a name (e.g. "Production server" or "Zapier").
  4. The response shows the client-id + api-key exactly 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
HeaderTypeRequired?Notes
client-idstringyesThe customer-id half of your API credential.
api-keystringyesThe secret half. Treat like a password.
X-Workspace-IdstringoptionalOverride the default workspace. Caller must be a member.
Content-Typestringconditionalapplication/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

StatusCause
400Missing client-id or api-key header.
401Invalid credentials (key revoked, wrong env, or key/client-id pair don't match).
403Key valid but doesn't grant access to the requested resource (e.g. cross-workspace target).
404API 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.