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
| Environment | Base URL |
|---|---|
| Sandbox | https://sandbox-api.coris.ai/api/v0_2 |
| Production | https://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,
ein123456789onPOST /business/verification/stays at206and never reaches200. 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-Versionheader, for example2024-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 GET | Meaning | Action |
|---|---|---|
200 | Finished. | Use the result. |
206 | Partial results. Some tasks are still running. | Store them and keep polling. |
425 | Still running, or waiting for manual review. | Keep polling. |
429 | Rate limited. | Wait, then keep polling. |
500 | The run failed. Coris queues it again. | Keep polling until the attempt cap. |
400 | The request is invalid. | Fix the request. |
404 | No 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.
| Property | Callback | Webhook |
|---|---|---|
| Set up with | callback_url on an async POST | A webhook endpoint from POST /webhooks/ |
| Signature header | X-CORIS-SIGNATURE: <hex> | Coris-Signature: t=<unix>,sha256=<hex> |
| Signed content | <raw_body> | <t>.<raw_body> |
| Algorithm | HMAC-SHA256 | HMAC-SHA256 |
| Deduplicate on | X-Request-ID header | id in the body |
| Attempts | Up to 32 | Up to 10 |
| Wait between attempts | 30 seconds, growing to 30 minutes | 2 seconds, growing to 256 seconds |
- Return any
2xxto 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_urland for webhook endpoints.
Rate limits
| Limit | Value | Applies to | Retry-After |
|---|---|---|---|
| Request rate | 1,000 requests per minute | Each Coris user. Keys from one organization can share a user. | Yes |
| Concurrent reads | 25 at once by default | Data GET calls without request_id, per organization | No |
Both limits return 429. Calls that start or poll a run do not count toward the concurrent limit.
Retries
| Result | Action |
|---|---|
GET, network error or timeout | Retry with backoff. |
GET, 502, 503, or 504 | Wait for Retry-After when it is present. Otherwise back off. |
429 | Wait for Retry-After when it is present. Otherwise back off. |
POST, any 5xx or unknown outcome | Follow the list under Idempotency. |
400, 401, 403, 404, 409, or 422 | Do 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 ontransaction_id.201means created, and200means updated. Resend the sametransaction_id.POST /accounts/needs areference_idthat is unique in your organization. A repeat returns409. On409, callGET /accounts/{reference_id}/. If that returns404, the account was deleted and keeps itsreference_id, so use a new one.- An async
POST, such asPOST /merchant_profiler/, starts a new run with a newrequest_idon each call. Keep the firstrequest_id. Resend only if norequest_idcame 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.
Updated about 1 hour ago