Pagination and Errors
Every list endpoint on the Amdahl Platform API follows the same shape, and every error response follows the same envelope. Code once against the conventions on this page and you will not have to special-case individual tools.
List endpoint conventions
The public list endpoints — GET /chats, GET /routines, GET /agents — share the same paging parameters:
| Parameter | Type | Default | Max | Purpose |
|---|---|---|---|---|
limit | integer | 50 | 200 | Page size |
offset | integer | 0 | n/a | Zero-indexed offset |
And the same envelope shape: a named collection array plus the paging facts (values are echoed back clamped if you overshot):
{
"chats": [ ... ],
"total": 147,
"limit": 50,
"offset": 0
}- The collection key matches the resource:
chatson/chats,routineson/routines,agentson/agents. total: total matching rows across all pages. Authoritative; safe for UI pagers.- There is no
has_morefield — derive it client-side:offset + rows.length < total. GET /agentsreturns the merged roster (Amdahl library + workspace agents) without paging — it is bounded small by construction.
Paging example
# First page
curl -s "$AMDAHL_BASE/chats?limit=50&offset=0" \
-H "X-API-Key: $AMDAHL_KEY"
# Next page
curl -s "$AMDAHL_BASE/chats?limit=50&offset=50" \
-H "X-API-Key: $AMDAHL_KEY"A client-side pager can simply walk offset by limit while offset + rows.length < total.
Filtering and sorting
Filters are per-endpoint, not a generic vocabulary:
| Endpoint | Filters |
|---|---|
GET /chats | status (e.g. ?status=complete) |
GET /routines | enabled (e.g. ?enabled=true) |
Ordering is fixed per endpoint — Chats list newest activity first, Routines newest first — there is no order_by parameter on the public surface. An unsupported query parameter is ignored or returns invalid_argument, never silently reinterpreted.
Error envelope
Every non-2xx response uses the same JSON envelope:
{
"error": {
"code": "invalid_input",
"message": "Human readable description of what went wrong.",
"details": {
"field": "scopes",
"issue": "must be a non-empty array"
}
}
}code: a machine-readable string from the catalogue below.message: a short human-readable description. Safe to surface in UIs.details: optional object with structured context. Shape depends oncode.
Clients should branch on code, never on the exact text of message. HTTP status is included but is a coarser signal than code.
Standard error codes
| Code | HTTP | When | Expected client reaction |
|---|---|---|---|
unauthenticated | 401 | Missing or invalid credential | Reauthenticate, do not retry the exact call |
forbidden | 403 | Credential valid but lacks required scope or access to the resource | Check details.missing_scope, rotate with higher scopes or stop |
not_found | 404 | Target resource does not exist or is outside the caller's business | Treat as terminal; do not retry |
invalid_input | 400 | Request body failed schema validation | Fix the payload; do not retry the same body |
invalid_argument | 400 | Syntactically valid request whose semantics are wrong (bad sort field, out-of-range values) | Fix arguments; do not retry the same call |
conflict | 409 | State conflict (for example revoking a key already revoked, double-approving an outline) | Refetch current state; reconcile |
rate_limited | 429 | Rate limit exceeded | Back off; see rate-limits.md |
internal | 500 | Server error | Retry with backoff for 5xx only |
The full, continuously-generated catalogue of codes plus details keys lives at api-reference/errors.md.
Retry strategy
- Retry:
internal(5xx) andrate_limited(429). Use exponential backoff with jitter. Start at 500ms, double each attempt, cap at 30 seconds, give up after 5 attempts unless your workload can tolerate more. - Do not retry: every other
4xx.invalid_input,forbidden,not_found, andconflictall mean "this exact call will not succeed". Retrying wastes quota and can triggerrate_limitedon top of the original failure. - Honor
Retry-After. When present on a 429 or 503 response (in seconds), wait that long before the next attempt.
Example rate_limited body:
{
"error": {
"code": "rate_limited",
"message": "Rate limit exceeded for POST /search.",
"details": {
"limit": 60,
"window_seconds": 60,
"retry_after_seconds": 12
}
}
}Idempotency
POST endpoints that create resources (POST /chat, POST /routines, POST /agents) are not yet idempotent at the server level. A successful-but-timed-out first attempt plus a retry can create two records (two Chats, two routines).
Until a server-side Idempotency-Key header lands (tracked in changelog.md as a planned feature), deduplicate on the client:
- For Chats: name the Chat deterministically and list with
GET /chatsbefore retrying a START; if the named Chat already has a fresh run, poll it instead of starting another. - For agents: pass an explicit
slugon create — a retry that raced a success returns a typedslug_conflictinstead of creating a duplicate, which makes create effectively idempotent per slug. - For routines: list
GET /routinesand match onnamebefore re-creating.
PATCH updates, DELETEs, resume, and cancel are naturally idempotent and safe to retry — a repeat resume against a run that already resumed returns invalid_state, not a double-apply.
Reference
- Full error code catalogue with
detailsshapes: api-reference/errors.md. - Rate limit windows and headers: rate-limits.md.
- Per-tool filter, sort, and response shapes: the tool catalog and the API reference.