Errors and limits
Error body
Each non-2xx response has the same body:
{
"error": "Bad Request",
"code": "validation_failed",
"message": "Request body failed validation",
"details": {
"formErrors": [],
"fieldErrors": { "user_email": ["Invalid email"] }
}
}
| Field | Use |
|---|---|
code | Stable. Use this field in your code. |
message | For people. It can change. Do not parse it. |
error | The HTTP status label. For information only. |
details | Only on a validation_failed where the body does not match the request schema. fieldErrors lists the errors for each field, and formErrors the errors on the body as a whole. |
Codes
| HTTP | code | Cause | Retry |
|---|---|---|---|
| 400 | validation_failed | The body or a query parameter is not valid. message says what is wrong, and details lists the failing fields when the body does not match the request schema. | No |
| 400 | invalid_json | The body is not valid JSON. | No |
| 400 | body_required | No JSON body, or Content-Type is not application/json. | No |
| 400 | document_path_invalid | A storage_path is not in the upload folder for this entity and partner_claim_id, or a message names a file already attached to the claim. | No |
| 400 | document_not_uploaded | A file in document_refs is not in storage. The upload did not complete. | No |
| 401 | auth_* | See Auth errors. | No |
| 403 | auth_partner_not_approved | Garfield has not approved your partner account yet. Until it does, only test keys work. | No |
| 403 | entity_not_owned | The entity is not connected to you and is not your own business. | No |
| 403 | partner_disconnected | The entity disconnected from you. You cannot read or write its claims. | No |
| 403 | entity_not_verified | Garfield has not verified the business yet, or has rejected it. Uploads, claims and messages fail, and reads still work. Send the failed requests again once GET /entities shows verified: true. | No |
| 404 | entity_not_found | No entity has this id, or you sent a real entity_id with a test key. | No |
| 404 | claim_not_found | You have not created a claim with that claim_id using a key of this mode, or you can no longer reach its entity. | No |
| 409 | claim_conflict | An internal id collision. This is rare. | Yes |
| 409 | idempotency_key_reused | You sent a partner_user_id, partner_claim_id or partner_message_id again with a different body. Garfield added nothing. | No |
| 409 | claim_archived | The business archived the claim, so you cannot add a message to it. | No |
| 409 | partner_user_id_mismatch | The partner_user_id is not the one the entity connected with. Check the entity_id. | No |
| 422 | onboarding_unavailable | Garfield cannot onboard the business automatically. Send a connect link. | No |
| 422 | company_not_found | Companies House has no company with this company_number. | No |
| 422 | company_not_active | The company is dissolved, in liquidation or otherwise not active at Companies House. | No |
| 422 | company_type_not_supported | The company is a type of organisation Garfield does not onboard. | No |
| 422 | business_type_mismatch | business_type differs from the company's type at Companies House. | No |
| 429 | rate_limited | Too many requests. See Rate limits. | Yes |
| 500 | internal_error | Server error. | Yes |
| 503 | service_unavailable | A dependency is temporarily unavailable. | Yes |
| 503 | document_verification_failed | The API could not check an uploaded document in storage. The fault is temporary. | Yes |
Retries
- Retry only the codes marked Yes. Wait for
Retry-Afterif the response has it. Use exponential backoff with jitter. - For all other codes, correct the request before you send it again.
- When you retry a create, send the same
partner_user_id,partner_claim_idorpartner_message_id, so the API does not create a second record. - Do not resend a request that returned
idempotency_key_reused. It fails again. To correct or add to a claim, send a message.
Rate limits
Each partner can send 120 requests per minute. All endpoints and all of your keys share this limit. Only requests with a verified key count, so no one else can use up your limit by sending your partner id with a wrong secret. Until Garfield approves your application, the limit is 30 requests per minute and each upload can be at most 5 MB.
Requests that fail authentication count separately:
10 per minute for each IP address. Past that, the API answers every failed
request with rate_limited until the minute is up. Fix the key before you send
more requests.
In production, every response to a request that passed authentication, with a
test or live key, has the X-RateLimit-Limit and X-RateLimit-Reset headers,
except a 503 service_unavailable when the API could not check the limit:
| Header | Meaning |
|---|---|
X-RateLimit-Limit | The maximum requests in each window. |
X-RateLimit-Reset | The window length in seconds, always 60. The API does not report how much of the current window is left. |
X-RateLimit-Remaining | Only on a 429, where it is 0. The API does not report a running count. |
If you go over the limit, the API returns 429 rate_limited with these
headers and Retry-After, the number of seconds to wait. Wait that long, then
retry. Keep a margin below X-RateLimit-Limit.
Idempotency
The API uses your ids as idempotency keys. There is no Idempotency-Key
header. Each partner has its own id space, so your ids never collide with
another partner's.
| Endpoint | Key | Replay result |
|---|---|---|
POST /onboardings | partner_user_id | HTTP 200, the first onboarding_id and entity_id, already_existed: true. No new email. |
POST /claims | partner_claim_id, within each entity | HTTP 200, the first claim_id, message_id and document_ids, already_existed: true. No new message or documents. |
POST /claims/{claim_id}/messages | partner_message_id, optional, within each claim | HTTP 200, the first message_id and document_ids, already_existed: true. Without it, every call adds a message. |
POST /connect-requests | None | Each call gives a new link. |
- The same
partner_claim_idwith a differententity_idcreates a separate claim. - A replay must send the same body. A different body returns
409idempotency_key_reused, and Garfield keeps what the first request created and adds nothing. The API ignores the order of fields. Items in an array such asinvoicesmust stay in the same order. The API treats a field sent asnullor[]the same as a field left out. A claim or onboarding created before Garfield began storing request bodies replays whatever body you send. - A
POST /claimsreplay goes through the same checks as a create. If the entity disconnected from you, the replay returns403partner_disconnected, and a differentpartner_user_idreturns409partner_user_id_mismatch. - A claim created before Garfield began storing message and document ids
replays with
message_id: nullanddocument_ids: []. - A replay of
POST /onboardingsdoes not send the magic link again, and the API never returns it. Anotherpartner_user_idfor the sameuser_emailreturns422onboarding_unavailable. If the link has expired, the user can open it and ask for a new one, which Garfield emails to the same address.