Handle errors

Coris API status codes, the error body formats you receive, and the exact fix for each error.

Branch on the status code, because the error body varies by endpoint. Read the body as text, and parse JSON only if the Content-Type is application/json.

  1. StartResponse
  2. CheckStatus
  • 2xx, 206, 425Continue or keep polling
  • 400, 404, 409, 422Fix the request. Do not retry.
  • 401, 403Fix the key or access
  • 429, 503Wait, then retry
  • 500Retry with backoff

Read an error

  1. Set your base URL and API key, so that the samples below run.
export CORIS_BASE_URL="https://sandbox-api.coris.ai/api/v0_2"
export CORIS_API_KEY="YOUR_API_KEY"
import requests

BASE_URL = "https://sandbox-api.coris.ai/api/v0_2"
HEADERS = {"Authorization": "Token YOUR_API_KEY"}
const BASE_URL = "https://sandbox-api.coris.ai/api/v0_2";
const HEADERS = {
  Authorization: "Token YOUR_API_KEY",
  "Content-Type": "application/json",
};
  1. Send a bad request and read the error. This body omits the address.
curl -s -i -X POST "$CORIS_BASE_URL/merchant_profiler/" \
  -H "Authorization: Token $CORIS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "business_name": "Acme Plumbing Co." }'
response = requests.post(
    f"{BASE_URL}/merchant_profiler/",
    headers=HEADERS,
    json={"business_name": "Acme Plumbing Co."},
)
print(response.status_code, response.headers.get("Correlation-ID"), response.text)
const bad = await fetch(`${BASE_URL}/merchant_profiler/`, {
  method: "POST",
  headers: HEADERS,
  body: JSON.stringify({ business_name: "Acme Plumbing Co." }),
});
console.log(bad.status, bad.headers.get("Correlation-ID"), await bad.text());

Try POST /merchant_profiler/ in the API explorer.

  1. Wrap your calls so that every format becomes one error shape.
# Prints the status, then the body, for any format.
curl -s -w '\nHTTP %{http_code}\n' "$CORIS_BASE_URL/accounts/not-a-real-account/" \
  -H "Authorization: Token $CORIS_API_KEY"
class CorisError(Exception):
    def __init__(self, response):
        self.status = response.status_code
        self.correlation_id = response.headers.get("Correlation-ID")
        is_json = "application/json" in response.headers.get("Content-Type", "")
        self.body = response.json() if is_json else response.text
        super().__init__(f"{self.status}: {self.body}")


response = requests.get(f"{BASE_URL}/accounts/not-a-real-account/", headers=HEADERS)
if not response.ok:
    print(CorisError(response))
async function corisError(response) {
  const isJson = (response.headers.get("content-type") || "").includes("application/json");
  return {
    status: response.status,
    correlationId: response.headers.get("correlation-id"),
    body: isJson ? await response.json() : await response.text(),
  };
}

const missing = await fetch(`${BASE_URL}/accounts/not-a-real-account/`, { headers: HEADERS });
if (!missing.ok) console.log(await corisError(missing));

Try GET /accounts/{account_id}/ in the API explorer.

Read the response

A failed request gives you a status, a Correlation-ID header and a body.

HTTP/1.1 422 Unprocessable Entity
Content-Type: text/html; charset=utf-8
Correlation-ID: 9f2c6b1e40a74c138d527ae1f03b6c28

An address is required unless website is provided and online=True
PartMeaningWhat to do
StatusThe request failed validation.Do not retry it unchanged.
Correlation-IDThe ID of this request in Coris logs.Log it, and quote it when you contact support.
BodyThe reason, as text.Show it to a developer, not a merchant.

Error formats

FormatWhenExample
JSON detailAuthentication, rate limits and malformed JSON{"detail": "Request was throttled."}
Plain textMost validation and not-found errorsNo account found for id acme-1
JSON field errorsPerson validation{"first_name": ["This field is required."]}
JSON errorFile uploads{"error": "file_name and file_type are required"}
JSON error and error_codeAccount list timeouts{"error": "...", "error_code": "accounts_query_timeout"}

Some routes use another JSON key, such as errors, so log the whole body. Plain-text errors arrive with Content-Type: text/html. A 500 may be plain text or HTML.

Status codes

Handle 401, 403 and 429 on every route.

StatusCauseFix
400A field is missing or invalid, or the body is not valid JSON.Read the body, then correct the request.
401The key is missing or invalid.Send Authorization: Token YOUR_API_KEY with a key for this host.
403The feature is off, a scoped key cannot use this route, or the key's user lacks a permission.Read the body, which names any missing permission. Then ask Coris to turn it on.
404The ID does not exist in your workspace.Check the ID and the environment.
409The reference_id already exists. A deleted account keeps its reference_id.Read that account. If you deleted it, create the account with a new reference_id.
422The name and address combination is invalid.Match address_type to the name you sent.
425The async result is not ready.Poll again. See Handle async requests.
429You passed a rate limit.Wait for Retry-After, then retry. See Handle rate limits and pagination.
500Coris failed to process the request.Retry a GET with backoff. Check a POST first, as the FAQ explains.
503A list query timed out.Wait for Retry-After, then retry or narrow the filters.

A malformed ID or parameter can return an HTML 500 instead of 400. Check IDs before you send them.

What to do next

Error typeRetry?How
500 on GETYesBack off and cap your attempts, as the poll helper in Handle async requests does. A malformed ID ends at that cap.
500 on POSTOnly if safeCheck whether the first attempt worked, then follow the FAQ.

FAQ

Will error formats change?

They may. Keep the wrapper above so that any change stays in one place.

Is it safe to retry a POST after a timeout?

There is no idempotency key on these endpoints, so the effect of a retry depends on the route.

  • An async POST, such as Merchant Profiler, starts a new job.
  • POST /accounts/ returns 409 if the first attempt worked.
  • POST /webhooks/ returns 500 if the first attempt worked, so list the endpoints before you retry.
  • POST /account_tags/ returns 500 if the first attempt worked, so list the tags before you retry.
  • POST /accounts/{account_id}/persons/ adds another person each time, so list the persons before you retry.
  • POST /accounts/{account_id}/transactions/ updates the row with the same transaction_id, so a retry is safe.

Why do I see 404 for an ID I can see in another environment?

Sandbox and production hold separate data. Check your host.

Next: Handle rate limits and pagination


Did this page help you?