Get started
Requests and errors
Conventions every endpoint follows: response shapes, error codes, pagination, safe retries and rate limits.
Responses
Send JSON bodies with Content-Type: application/json. Successful responses put the result in data and call details in meta. meta.requestId (also the X-Request-Id header) identifies the call; include it when you contact support.
{
"data": {
"id": "7c1e9f52-3b8a-4d2e-9a61-5f0c2d8b4e17",
"name": "Jamie Rivera",
"...": "..."
},
"meta": {
"requestId": "req_1f0b6c2e9a7d4f58b3c1e6a9d2f4b7c0"
}
} Timestamps are ISO 8601 in UTC. Client, request, webhook and delivery IDs are UUIDs; call and event IDs look like req_... and evt_.... Fields with no value are null rather than missing, except where noted (such as a webhook's secret, returned only when it is created or rotated). Deleting returns 204 with no body. New fields can be added to responses at any time, so ignore fields you don't know. Bodies can be up to 100 KB.
Errors
Errors use HTTP status codes and a stable error.code to branch on. error.message is written for people and can change; error.details lists the problems found, with a field when one applies.
{
"error": {
"code": "VALIDATION_FAILED",
"message": "The request is invalid.",
"details": [
{
"field": "email",
"message": "Invalid email"
}
]
},
"meta": {
"requestId": "req_8a2d4c6e0b1f3a5c7e9d1b3f5a7c9e1d"
}
} | Status | Code | Meaning |
|---|---|---|
| 400 | VALIDATION_FAILED | The body, query or a header is invalid; details lists the problems. |
| 413 | VALIDATION_FAILED | The body is larger than 100 KB. |
| 401 | AUTHENTICATION_REQUIRED, INVALID_API_KEY, API_KEY_REVOKED | See Authentication. |
| 403 | CAPABILITY_REQUIRED | The key lacks the scope this call needs. |
| 403 | PLAN_NOT_ENTITLED | Your plan does not include API access. |
| 403 | PLAN_LIMIT_REACHED | This month's access link limit is reached. |
| 404 | NOT_FOUND | It does not exist, or it belongs to another agency. |
| 409 | EXTERNAL_CLIENT_ID_CONFLICT | Another client already has this externalClientId. |
| 409 | EXTERNAL_CLIENT_ID_IMMUTABLE | The client's externalClientId is already set. |
| 409 | IDEMPOTENCY_CONFLICT | The Idempotency-Key was already used with a different body, or the object it created no longer exists. |
| 409 | IDEMPOTENCY_IN_PROGRESS | The first call with this key is still running; retry shortly. |
| 409 | INVALID_STATE | The request is in a state that does not allow this, such as cancelling a completed request. |
| 429 | RATE_LIMITED | Too many calls; wait for Retry-After seconds. |
| 500 | INTERNAL_ERROR | Something went wrong on our side; retry with backoff. |
Request bodies, and the query parameters of list endpoints, are validated strictly: unknown fields and parameters return 400 VALIDATION_FAILED instead of being ignored, so typos surface immediately. Names, emails and other free text are trimmed; IDs are not, and an externalClientId that starts or ends with whitespace is rejected.
Pagination
Client, request and webhook delivery lists are paginated: they take limit (1 to 100, default 50; other values return 400) and return up to that many items. When there are more, meta.nextCursor is set: pass it as cursor with the same filters to get the next page. It is null on the last page. Cursors are tied to the filters they were issued for; using one with different filters returns 400. Webhooks, accounts and services are always returned in full.
let cursor = null
do {
const url = new URL("https://api.agencyaccess.co/api/v2/requests")
url.searchParams.set("status", "in_progress")
if (cursor) url.searchParams.set("cursor", cursor)
const res = await fetch(url, { headers: { Authorization: `Bearer ${process.env.AGENCYACCESS_API_KEY}` } })
const { data, meta, error } = await res.json()
if (!res.ok) throw new Error(`${error.code}: ${error.message}`)
for (const request of data) handle(request)
cursor = meta.nextCursor
} while (cursor) Idempotent creates
Creating a client, request or webhook requires an Idempotency-Key header with a unique value per create, such as a UUID. If the network fails, retry with the same key and body:
- If the first call created something, the retry returns that object with the header
Idempotent-Replayed: true. Nothing new is created and your monthly allowance is not used again. - The same key with a different body returns
409 IDEMPOTENCY_CONFLICT, as does a retry after the object it created was deleted. - A retry while the first call is still running waits up to about 10 seconds for it, then returns
409 IDEMPOTENCY_IN_PROGRESS; retry shortly. A first call that has held the key for over a minute is treated as abandoned, and the retry goes ahead. - If the first call failed without creating anything (for example with
400), the key is released: retry with the same key and a corrected body.
Keys are 1 to 255 visible ASCII characters; a missing or invalid key returns 400 VALIDATION_FAILED. Keys are kept for 24 hours and are scoped to your API key and the operation; after that, the same key creates a new object. A webhook's signing secret is not included in replays.
Rate limits
Your agency can make 300 calls a minute across all its keys, and sending test webhook events is limited to 10 a minute. Each IP address can also make 600 calls a minute. Responses include RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset (seconds until the window resets) for your agency's limit, or for the test limit when sending test events. Over a limit, calls return 429 RATE_LIMITED with a Retry-After header in seconds. Rather than polling, use webhooks to learn about changes.
Versioning
This is version 2 of the API, under /api/v2. Within a version we only make additive changes: new endpoints, new optional fields and new event types. Breaking changes come in a new version, announced in advance. Webhook event bodies carry a schemaVersion (currently 2026-10-06).