Rules for coding agents

Follow these rules when an AI coding agent writes code that calls the Coris API.

Apply these rules so your code authenticates, polls, retries and writes to the Coris API without failed calls.

Full docs: docs.coris.ai/llms.txt. Every page is also served as Markdown at <url>.md.

Authentication

This call proves a key works.

curl -s "https://sandbox-api.coris.ai/api/v0_2/accounts/?limit=1" \
  -H "Authorization: Token $CORIS_API_KEY"
  • Send Authorization: Token <key> on every request.
  • Read the key from an environment variable or secret store.
  • Never commit, log, or print a key. Never put one in client-side code.
  • Create sandbox keys at sandbox.coris.ai and production keys at portal.coris.ai.
  • Use each key only with its own environment.

Environments

EnvironmentBase URL
Sandboxhttps://sandbox-api.coris.ai/api/v0_2
Productionhttps://api.coris.ai/api/v0_2
  • Use the sandbox by default. Use production only when your user asks for it.
  • Use spec paths exactly as written, including the trailing slash.
  • In the sandbox, ein 123456789 on POST /business/verification/ stays at 206 and never reaches 200. Use it only to test timeouts.

Versioning

  • The version is in the path: /api/v0_2. Requests have no version header.
  • The docs may show the label v1. It is not a path, so do not call /api/v1.
  • Webhook deliveries carry a Coris-Api-Version header, for example 2024-08-23.

Async requests

An async POST returns 201 with a request_id. Poll the same path with GET ?request_id=<id>.
POST /accounts/ can return the account itself, so check the body for request_id.

Status on GETMeaningAction
200Finished.Use the result.
206Partial results. Some tasks are still running.Store them and keep polling.
425Still running, or waiting for manual review.Keep polling.
429Rate limited.Wait, then keep polling.
500The run failed. Coris queues it again.Keep polling until the attempt cap.
400The request is invalid.Fix the request.
404No such request for this key on this path.Stop. Check the ID and path.

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

request_id is a UUID. A malformed one looks like a retryable 500, so polling ends at the attempt cap.

Runs can take several minutes. Cap polling by attempts.
Start with 30 attempts, about 14 minutes at this backoff, and adjust the cap to your use case.

import time

import requests


def poll(url, request_id, headers, attempts=30):
    delay = 5
    for _ in range(attempts):
        time.sleep(delay)
        response = requests.get(url, params={"request_id": request_id}, headers=headers, timeout=30)
        if response.status_code == 200:
            return response.json()
        if response.status_code not in (206, 425, 429, 500, 502, 503, 504):
            response.raise_for_status()
        delay = min(delay * 1.5, 30)
    raise TimeoutError(f"Request {request_id} did not finish")

Callbacks and webhooks

Coris has two delivery paths. They use different headers, so verify each one its own way.
Compute each signature over the raw request body, and compare with a constant-time function.

PropertyCallbackWebhook
Set up withcallback_url on an async POSTA webhook endpoint from POST /webhooks/
Signature headerX-CORIS-SIGNATURE: <hex>Coris-Signature: t=<unix>,sha256=<hex>
Signed content<raw_body><t>.<raw_body>
AlgorithmHMAC-SHA256HMAC-SHA256
Deduplicate onX-Request-ID headerid in the body
AttemptsUp to 32Up to 10
Wait between attempts30 seconds, growing to 30 minutes2 seconds, growing to 256 seconds
  • Return any 2xx to acknowledge a delivery. Every other response counts as a failure.
  • Webhook attempts time out after 10 seconds. Queue slow work and acknowledge first.
  • Expect duplicates. Handle each delivery so that a repeat changes nothing.
  • A failed run sends no callback. Keep a polling fallback for runs that never report back.
  • Use a public HTTPS URL for callback_url and for webhook endpoints.

Rate limits

LimitValueApplies toRetry-After
Request rate1,000 requests per minuteEach Coris user. Keys from one organization can share a user.Yes
Concurrent reads25 at once by defaultData GET calls without request_id, per organizationNo

Both limits return 429. Calls that start or poll a run do not count toward the concurrent limit.

Retries

ResultAction
GET, network error or timeoutRetry with backoff.
GET, 502, 503, or 504Wait for Retry-After when it is present. Otherwise back off.
429Wait for Retry-After when it is present. Otherwise back off.
POST, any 5xx or unknown outcomeFollow the list under Idempotency.
400, 401, 403, 404, 409, or 422Do not retry. Fix the cause.

Back off from 1 second to a 30-second cap, and add jitter.
Quote the Correlation-ID response header in any support request.

Idempotency

The API does not support idempotency keys. /api/v0_2 ignores an Idempotency-Key header.
Use these rules for a POST that may have failed.

  • POST /accounts/{account_id}/transactions/ upserts on transaction_id. 201 means created, and 200 means updated. Resend the same transaction_id.
  • POST /accounts/ needs a reference_id that is unique in your organization. A repeat returns 409. On 409, call GET /accounts/{reference_id}/. If that returns 404, the account was deleted and keeps its reference_id, so use a new one.
  • An async POST, such as POST /merchant_profiler/, starts a new run with a new request_id on each call. Keep the first request_id. Resend only if no request_id came back, and accept a possible second run.
  • POST /accounts/{account_id}/persons/ adds a new person on each call. List the persons first, and resend only if yours is missing.

For any other write, read the current state before you resend it.

Data handling

  • Never send a Social Security Number in an EIN field.
  • Use fictional businesses and people in sandbox tests and sample code.
  • Do not log personal data or full response bodies.

Did this page help you?