Error Handling
Error handling patterns — retry decisions, immutability, CANCEL and recreate, deduplication, and rate limits.
Last updated on
This guide covers operational recovery patterns for integrating with the Carrot API.
For endpoint error codes and payload format, see API Errors.
Classify failures first
4xx: request/data/integration issues. Fix payload or flow.5xx: transient platform or upstream issues. Retry with backoff.
Retry decision table
| Status Code | Error | Action |
|---|---|---|
400 | Validation error | Abort — fix the payload and resubmit |
403 | Token expired (gateway body) | Refresh the token and retry once |
401 / 403 | Other authentication/authorization | Abort — check credentials or permissions |
404 | Resource not found | Abort — verify IDs in the request path |
409 | PENDING_PROCESS_CONFLICT_ERROR | Retry after 2-5 seconds — a background process is in progress |
409 | Duplicate deduplicationId | Stop — your earlier attempt succeeded; do not retry or resubmit |
409 | Other conflict errors | Abort — fix the data conflict, do not retry as-is |
422 | Unprocessable entity | Abort — fix the payload structure |
429 | Rate limit exceeded | Retry with exponential backoff |
500 | Internal server error | Retry with exponential backoff + deduplicationId |
502 / 503 / 504 | Gateway/service unavailable | Retry with exponential backoff + deduplicationId |
Retry strategy
- Use bounded exponential backoff for transient failures (start at 1s, max 30s, max 5 retries).
- Reuse
deduplicationIdon retryable create/event calls — see below. - Stop retrying on deterministic validation failures — every
4xxexcept409 PENDING_PROCESS_CONFLICT_ERROR,429, and the expired-token403, which is resolved by refreshing the token rather than by retrying the same request.
deduplicationId mechanics
The deduplicationId is a create-once key for write operations:
- Generate a unique ID before the first attempt
- Store it alongside the operation in your system
- Reuse the same ID on every retry of that operation
The ID is scoped per Network Integrator. This is critical for
POST /documents and POST /documents/{id}/events.
A replay is rejected, not replayed
If the server already processed your request, the retry is rejected with 409 CONFLICT_ERROR
(Document deduplicationId: (…) already exists). That conflict is the confirmation that the
original write succeeded and no duplicate was created — it is not an error to fix and not
a signal to retry as-is. The response does not carry the original document or event id.
A lost POST /documents response is therefore not recoverable through the API on your own. The only
document read is GET /documents/{id}: there is no lookup by externalId or deduplicationId, and
the retry's 409 does not return the id. Send externalId anyway — it is what Carrot support needs
to locate the document for you — but treat recovering the id as a support request, not a
self-service call. There is no safe way to repeat the write yourself: reusing the
deduplicationId returns the 409 without the id, and using a new one creates a second document.
Record the id from the first successful response before doing anything else, and keep the request
timeout generous enough that a slow commit does not look like a lost one.
Immutability recovery pattern
The document and event API is intentionally append-only: new events record changes in state while earlier submissions remain in the history. See Core Concepts for this design. When an incorrect event has been accepted, the correction flow is to cancel that document and submit a replacement with the correct data and events.
CANCEL and recreate
An UPDATE event follows the same design: it records a change to document visibility or public
searchability, rather than replacing previously submitted data. Correcting a participant, a value,
or an earlier event therefore uses cancellation and a new submission. The public API exposes no
document PATCH, PUT, or DELETE operation.
Before cancelling or reimporting a real document, check its downstream certificates, credits, and audit state with the responsible team. Confirm the recovery procedure and how those records will be reconciled; cancellation alone does not establish that downstream effects have been reversed.
Choose the relationship direction according to where the relationship needs to be recorded:
- Unidirectional (
bidirectional: false): appends the reference only to the document addressed by the request. The referenced document receives no new event. Use this when one document needs to cite another without adding a reciprocal record to its timeline. - Bidirectional (
bidirectional: true): also appends a back-reference event to the referenced document (RELATEDby default). Use this when the relationship needs to appear in both timelines. Both documents must be able to accept their respective events. This extends their histories; it does not rewrite existing events or make the documents interchangeable.
Both modes require the same dataset. A RELATED event can be added after CLOSE, but no new
event can be added after CANCEL. A bidirectional relationship to a cancelled document is
therefore rejected because it would append a back-reference to that document.
In replacement recovery, the new document records which original it replaces, while the cancelled original receives no further events. The example uses a unidirectional historical reference for that purpose. Once the recovery procedure is confirmed, submit the corrected data in the same dataset and supply the remaining required event fields:
1. POST /documents/{wrongDocumentId}/events
{
"name": "CANCEL",
"metadata": {
"attributes": [
{ "name": "reason", "value": "Wrong participant on original document", "isPublic": true }
]
},
...
}
2. POST /documents
{ ...corrected document data... }
3. POST /documents/{newDocumentId}/events
{
"name": "RELATED",
"relatedDocument": {
"documentId": "{wrongDocumentId}",
"bidirectional": false
},
...
}A CANCEL event must carry a non-empty metadata attribute named reason (lowercase); omitting it returns 400 with
The reason metadata is required for CANCEL events. Every metadata attribute also requires its own
isPublic, so the attribute object must be complete.
Because no back-reference event is created in unidirectional mode, omit isPublic, eventName,
eventLabel, isBackReference, and preserveSensitiveData inside relatedDocument. The API
rejects these fields in this mode. The event itself still requires its own isPublic.
The reference preserves the connection between the two records; it does not repair downstream certificates, credits, or audit results. Retain both document IDs in your recovery record.
Rate limiting
The Carrot API enforces rate limits per API client (see Rate Limits for the full reference):
| Limit | Value |
|---|---|
| Sustained | 20 requests per second |
| Burst | 30 requests |
The limit is identical in test and production: the environment is selected by your credential, not by a separate throttle tier.
Implement a client-side token bucket or leaky bucket to stay within limits. When you receive a 429, apply exponential backoff before retrying.
Observability
Log these fields from every API response for debugging and support:
- Request ID — returned in response headers
- Your
externalId— your internal correlation ID - Error envelope fields —
errors[].code,errors[].id,errors[].timestamp - Retry count — how many attempts were made
- Terminal failure reason — why retries stopped