Authentication
The Expedify API authenticates with scoped API keys. Every request carries one, and the key's scopes decide what it can do.
The key
Authorization: Bearer agx_<your_key>
- Mint keys: Settings → API Keys (owner/admin). Pick the scopes the key needs in the scope picker → copy the
agx_…value. - Shown once. Keys are stored hash-only; copy the secret at creation. Lost it? Rotate.
- Per-organization. A key belongs to exactly one org and carries its tenant binding — it can never read or write another org's data.
- Rotate / revoke any time (Settings → API Keys). Revoking cuts off every client using that key immediately.
curl https://api.expedify.ai/v1/objects/contacts?limit=1 \
-H "Authorization: Bearer agx_your_key"
Scopes — default-deny
A key holds a set of scopes (contacts:read, deals:write, workflows:run, …). Every operation declares the scope it requires; no scope → 403. write / author / run imply read on the same resource. See the Scopes reference.
Honored only on /v1
API keys are authenticated exclusively on /v1. This is deliberate: /v1 is the one door, and the scope check on it is the one lock. Keys are not accepted on internal application routes.
Rate limits
Requests are rate-limited per key + per org. Responses carry:
| Header | Meaning |
|---|---|
X-RateLimit-Limit | Your window's ceiling |
X-RateLimit-Remaining | Requests left in the window |
X-RateLimit-Reset | When the window resets |
Over the limit → 429 with a Retry-After header. Back off and retry after it. (The SDKs do this automatically.)
Errors
Auth failures use the standard envelope:
{ "error": { "type": "unauthorized", "message": "Missing or invalid API key", "request_id": "…" } }
401 = missing/bad key · 403 = valid key, missing scope.