IntegrationsGuides

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 CodeErrorAction
400Validation errorAbort — fix the payload and resubmit
403Token expired (gateway body)Refresh the token and retry once
401 / 403Other authentication/authorizationAbort — check credentials or permissions
404Resource not foundAbort — verify IDs in the request path
409PENDING_PROCESS_CONFLICT_ERRORRetry after 2-5 seconds — a background process is in progress
409Duplicate deduplicationIdStop — your earlier attempt succeeded; do not retry or resubmit
409Other conflict errorsAbort — fix the data conflict, do not retry as-is
422Unprocessable entityAbort — fix the payload structure
429Rate limit exceededRetry with exponential backoff
500Internal server errorRetry with exponential backoff + deduplicationId
502 / 503 / 504Gateway/service unavailableRetry with exponential backoff + deduplicationId

Retry strategy

  • Use bounded exponential backoff for transient failures (start at 1s, max 30s, max 5 retries).
  • Reuse deduplicationId on retryable create/event calls — see below.
  • Stop retrying on deterministic validation failures — every 4xx except 409 PENDING_PROCESS_CONFLICT_ERROR, 429, and the expired-token 403, 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:

  1. Generate a unique ID before the first attempt
  2. Store it alongside the operation in your system
  3. 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 (RELATED by 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):

LimitValue
Sustained20 requests per second
Burst30 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

On this page