Guide 06 · Developer docs

The APIs: REST, GraphQL and custom endpoints

Every Core dna site answers three kinds of API request under /cdna-api: REST for everything, GraphQL for reads that span several entity types, and web events for endpoints you define yourself as jobs. Which fits which task, one worked request for each, and what is true of all three. Jobs were called hooks until the module was renamed Orchestration; the API's routes still use the old name.

L1–L3
Unit 02 · Writing content

Retries and writes that may have applied

A write that timed out may have happened. Which requests are safe to send again, what a repeat of the others does, measured on this site, and how to make a create, a collection edit and a cart change safe to retry.

Who this is for. The integrations engineer whose sync job runs unattended, and anyone writing the retry logic of an API client.

A timeout, a dropped connection or a 502 from a proxy tells you nothing about whether the platform made the change. The API has no idempotency key to send with a write, so a client stays safe in one of two ways: send only requests that do no harm when repeated, or read the record before repeating the ones that do.

What a repeat does

Measured on this site's API sandbox, by sending each request twice:

RequestSent twiceSafe to retry
Any GETThe same answerYes
PUT setting a record's own fields (name, slug, published, custom fields by name)The same values againYes
PUT with patchRevision and originRevisionThe same content, in a second identical revisionYes
PUT with patchRevision and no originRevision, adding to a collectionThe element added twiceNo
PUT with "activeRevision": <id>, a rollbackThe same revision liveYes
POST creating a recordTwo records; the second's slug gets -1No
DELETEThe second answers 404 "Page or record not found"Yes, reading the 404 as done
POST /cdna-api/cart/{uuid}/item/{sku}The quantity added twice: 3, then 4, then 5No
PUT /cdna-api/cart/{uuid}/item/{id} with a quantityThe same quantityYes
POST submitting a formA second submissionNo

Edits: pin the origin revision

A patch copies a revision and changes the copy. With originRevision, a repeat copies the same revision again and ends with the same content, so even a collection insert or append is safe to retry. Without it, the patch copies the newest revision, which after a first success already holds the change, and the change is made a second time:

Text The API sandbox page, one request per line
Body sent twice, each time                          Sections after it
--------------------------------------------------  ---------------------------------------------
"originRevision": 3447, append "Retry probe"        inserted · A · D · appended · Retry probe
"originRevision": 3447, append "Retry probe"        inserted · A · D · appended · Retry probe
(no origin), append "Retry probe"                    … appended · Retry probe · Retry probe
(no origin), append "Retry probe"                    … appended · Retry probe · Retry probe · Retry probe

So take originRevision from the read you based the edit on, and keep it for every attempt. It is the same rule that keeps an edit from undoing a rollback (Choosing a write), and it costs one extra revision per retry, which is harmless. Pin it for removals too: retried without one, a removal acts on a collection that has already lost an element. Even pinned, a position is only as good as the read it came from (Positions drift).

Creates: look before creating again

A create that may have happened has to be found before it is repeated, and the platform gives a create no key of its own until it answers. Give the record one: set externalId to the other system's id when creating it, and after a timeout, look for that id first:

Look for a record by the other system's id before creating it again
GET /cdna-api/custom-entities/apiSandboxes?externalId=crm-1001&properties={id,externalId} HTTP/1.1
Host: hub.coredna.com
Authorization: Bearer YOUR_ACCESS_TOKEN
Accept: application/json
Response
{
  "result": "success",
  "totalCount": 1,
  "data": [
    {"id": 2, "externalId": "crm-1001"}
  ]
}
{
  "result": "success",
  "totalCount": 0,
  "data": []
}
Real responses from this documentation site, on its API sandbox records

Pages, blog posts, custom entity records, products, categories, menus and menu elements, files, forms, users and people have externalId, a string of up to 190 characters. It is not unique: a second record with the same externalId was created without complaint, so the look-up is the client's job. A duplicated record does not copy it.

Where a record has no externalId, look it up by whatever the create set that nothing else shares: a slug under a known parent, a SKU, an email address.

Deletes: a 404 on a retry means done

A second DELETE of the same record answers 404 "Page or record not found". The same 404 is what a request without access gets, since the platform answers access denied with a 404 by default. When the first attempt timed out and the retry says 404, the record is gone either way; a client that has deleted other records with the same credentials can treat it as done.

Carts: set the quantity, do not add it

Adding a product to a cart adds its quantity to the line that is already there, so a retried add doubles it. After the first add, change the line with PUT /cdna-api/cart/{uuid}/item/{item id} and the quantity you want, which is the same however often it is sent. Never retry a purchase or a payment blindly: read the checkout first.

A retry policy

  • Retry automatically on a timeout, a dropped connection, a 502, 503 or 504, and only requests from the safe rows above, with a growing delay between attempts.
  • Read first, then decide, for a create, an add to a cart, a form submission and a collection edit without an origin.
  • Do not retry a 4xx unchanged. A 422 needs a different body, a 404 a different id or different access, and a 401 or 403 new credentials. An expired OAuth token is the one 4xx worth one retry, after refreshing it (Authentication).
  • Treat a 500 on a write like a timeout: read the record before sending it again.

A client that wraps all of this once, so the rest of the code never sees it, is described in Writing the client layer.

Go deeper

Was this page helpful?

Your answer goes to the docs team.