Errors and limits
Every REST refusal is a JSON body of exactly { code, message }. Branch on the code; show the message, which says what happened and what to do, and may change. Over MCP a refused tool call is a tool error whose structuredContent holds the same code and message.
Over MCP, a tool error for xero_not_connected or plan_required also carries url: the Collects page where the person fixes it. On MCP 2026-07-28, a host that declares URL elicitation is offered that link once instead, and shows it to the person before opening it.
| Code | Status | Meaning |
|---|---|---|
| 401 | No access token was sent. Sign in (OAuth) and send it as a Bearer token. | |
invalid_token | 401 | The access token is expired, revoked or not for this API. Refresh it, or connect again. A connection ends when its person's password is reset, they are deactivated, or they turn two-step verification on. |
insufficient_scope | 403 | The connection was not given a permission the call needs. The WWW-Authenticate challenge names every permission it needs; reconnect and grant them. |
xero_not_connected | 403 | Starting a resolution or creating a webhook subscription needs a live Xero connection. An owner or admin reconnects Xero in Collects. Over MCP the tool error carries the link as url. |
plan_required | 403 | The organisation's plan does not include this. Only the owner changes the plan, on collects.io; the message carries the link. Over MCP the tool error carries the link as url. |
role_required | 403 | The person's role in Collects cannot do this, as in the Collects app. The message says which role can. |
dunning_disabled | 422 | The organisation has Send collection emails switched off, so a resolution would contact nobody. An owner or admin turns it on in Settings. |
account_suspended | 403 | The organisation is suspended: reads still work, every change is refused until the owner resolves it on collects.io. |
connection_read_only | 403 | The person who connected the app set the connection to Read only. Reconnect with full access to make changes. |
not_found | 404 | No such record in this organisation, or not one this person can see. Also returned when the API is switched off for the organisation. |
resolution_open | 409 | A resolution is already open for this invoice. Follow it instead of starting another. |
idempotency_key_reused | 409 | This Idempotency-Key was used within 24 hours for a different request. Use a new key for a new request. |
not_resolvable | 422 | Collects cannot resolve this invoice now (closed, not yet due, or held); the message says which. |
invalid_request | 400 | An input is not one the Collects screen accepts, the screen does not offer that action in the record's current state, or Collects-Version names a version this API does not serve. The message says which. |
rate_limited | 429 | The connection's rate limit is spent. Wait the Retry-After seconds, then try again. |
method_not_allowed | 405 | The path exists but not with this HTTP method. |
internal_error | 500 | Something went wrong in Collects. Try again later. |
confirmation_mismatch | 409 | The confirmation token does not match the action as it stands now (what was previewed changed), or is not this person's and connection's. Preview again. |
confirmation_expired | 409 | The confirmation token is older than ten minutes. Preview again. |
confirmation_used | 409 | The confirmation token was already used. Preview again. |
Rate limits
Each connection (one person's connection of one app) has two buckets: reads, 120 a minute, and writes, 100 a minute, each refilling evenly over the minute. Every response to a signed-in call states its bucket in the IETF RateLimit-Policy and RateLimit headers, for example RateLimit-Policy: "read";q=120;w=60 and RateLimit: "read";r=119;t=1 (r calls left, t seconds until the bucket is full). A refused call answers 429 rate_limited with Retry-After.
Other headers
Collects-Version: sent to choose a version, and on every response the version that served it (Versioning).Idempotency-Key: on starting a resolution and on Send now, the first answer is replayed for 24 hours to a request with the same key.DeprecationandSunset: on every response of an operation announced for removal.