Every failed request returns the same JSON envelope, whatever went wrong. This guide reads that envelope on two common failures — two requests in total — and turns it into something your code can branch on.
Step 1: Read the envelope of a missing resource
Request a document that does not exist:
GET /documents/QvT8kXw2NcR5mZb4PdYj
Authorization: Bearer YOUR_API_KEY
HTTP/2 404
{
"status": 404,
"type": "DOCUMENT:NOT_FOUND",
"message": "Document not found",
"requestId": "a17c4e923b5d4f80b6e92d8c5a1f7e43"
}
The type is stable and machine-readable — branch on it, not on the message, which is human-readable and may be reworded. Quote the requestId when contacting support; it identifies this exact request in Circularo's logs.
Step 2: Read the envelope of an invalid request
Send a request that fails validation:
GET /documents?limit=0
Authorization: Bearer YOUR_API_KEY
HTTP/2 400
{
"status": 400,
"type": "VALIDATION:INPUT_INVALID",
"message": "Request validation errors: QUERY ERRORS: \"limit\" must be greater than or equal to 1",
"requestId": "5e28c7a190d44b3f8a6c1f7e42d95b03"
}
Same envelope, different class: a 4xx with a validation type means your request needs fixing before a retry makes sense, while a 404 may just mean "not yet" or "no longer". Server-side failures (5xx) are the class worth retrying — see Retry requests safely for doing that safely.
When you exceed the rate limit, the envelope arrives with status 429 and a Retry-After header telling you how many seconds to wait before retrying.
Complete example
One wrapper that turns every failure into the same typed error, as a Node.js script. Everything above it in your integration can then branch on type alone.
const BASE_URL = "https://sandbox.circularo.com/api/v1/public"; // your instance's base URL
const API_KEY = "YOUR_API_KEY";
const call = async (method, path, body) => {
const res = await fetch(`${BASE_URL}${path}`, {
method: method,
headers: { "Authorization": `Bearer ${API_KEY}`, "Content-Type": "application/json" },
body: (body === undefined) ? undefined : JSON.stringify(body)
});
if (res.ok) {
return (res.status === 204) ? null : await res.json();
}
const envelope = await res.json();
console.error(`Request ${envelope.requestId} failed with ${envelope.status}`);
if (res.status === 429) console.error(`Rate limited, retry in ${res.headers.get("Retry-After")}s`);
// the whole envelope travels with the error, so callers can branch on its type
throw Object.assign(new Error(envelope.message), envelope);
};
try {
await call("GET", "/documents/QvT8kXw2NcR5mZb4PdYj");
} catch (error) {
if (error.type === "DOCUMENT:NOT_FOUND") {
console.log("Nothing to do — the document is gone");
} else {
throw error;
}
}
Next steps
-
Retry the failures worth retrying, without creating duplicates — Retry requests safely.
-
Handle the
412that guards a concurrent write — Guard concurrent writes. -
The error codes each endpoint can return are listed in the API reference.