Handle errors

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.

Note

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.

JavaScript
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 412 that guards a concurrent write — Guard concurrent writes.

  • The error codes each endpoint can return are listed in the API reference.