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.

200 OK
{
  "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.

400 Bad Request
{
  "error": {
    "code": "VALIDATION_FAILED",
    "message": "The request is invalid.",
    "details": [
      {
        "field": "email",
        "message": "Invalid email"
      }
    ]
  },
  "meta": {
    "requestId": "req_8a2d4c6e0b1f3a5c7e9d1b3f5a7c9e1d"
  }
}
StatusCodeMeaning
400VALIDATION_FAILEDThe body, query or a header is invalid; details lists the problems.
413VALIDATION_FAILEDThe body is larger than 100 KB.
401AUTHENTICATION_REQUIRED, INVALID_API_KEY, API_KEY_REVOKEDSee Authentication.
403CAPABILITY_REQUIREDThe key lacks the scope this call needs.
403PLAN_NOT_ENTITLEDYour plan does not include API access.
403PLAN_LIMIT_REACHEDThis month's access link limit is reached.
404NOT_FOUNDIt does not exist, or it belongs to another agency.
409EXTERNAL_CLIENT_ID_CONFLICTAnother client already has this externalClientId.
409EXTERNAL_CLIENT_ID_IMMUTABLEThe client's externalClientId is already set.
409IDEMPOTENCY_CONFLICTThe Idempotency-Key was already used with a different body, or the object it created no longer exists.
409IDEMPOTENCY_IN_PROGRESSThe first call with this key is still running; retry shortly.
409INVALID_STATEThe request is in a state that does not allow this, such as cancelling a completed request.
429RATE_LIMITEDToo many calls; wait for Retry-After seconds.
500INTERNAL_ERRORSomething 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.

Reading every page (Node.js)
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).