Errors, rate limits & conventions
Every Trinitite endpoint — regardless of surface — shares the same error envelope, rate-limit contract, pagination scheme, and idempotency rules. This page is the single source of truth; per-endpoint pages document only their surface-specific extensions.
Error envelope
Every error response uses one JSON shape, regardless of HTTP status:
{
"error": {
"code": "validation_error",
"message": "human readable description",
"details": { },
"request_id": "req_01J9X..."
}
}
| Field | Type | Description |
|---|---|---|
error.code | string | Stable, snake_case, machine-readable. Safe to switch on. |
error.message | string | Human-readable. May vary across releases — never switch on it. |
error.details | object | null | Endpoint-specific structured context (validation paths, missing scopes, conflict markers). |
error.request_id | string | Correlates with the X-Request-Id header and the audit log. Always include it when reporting an issue. |
Common top-level codes:
| HTTP | error.code | Meaning |
|---|---|---|
400 | validation_error | Body or query failed schema validation. details lists field paths. |
400 | bad_request | Semantic precondition violated (e.g. editing a finalized policy). |
401 | unauthenticated | Missing or invalid credential. |
403 | forbidden | Authenticated but lacking the required permission. details.required_permission is set. |
403 | entitlement_denied | Caller's organization is missing a required entitlement or feature flag. |
404 | not_found | Resource does not exist or is outside the caller's organization. |
409 | conflict | Resource state precludes the transition (version race, delete-in-progress). |
409 | idempotency_conflict | Same idempotency key reused with a different request body. |
410 | resource_gone | Hard-deleted resource. |
422 | unprocessable_entity | Domain-level validation failed (e.g. Guardian not in ready state). |
429 | rate_limited | Per-organization rate limit exceeded. See § Rate limits. |
500 | internal_error | Unexpected platform error. Safe to retry with backoff. |
502 / 504 | upstream_error, upstream_timeout | Downstream LLM provider, MCP server, or training service failed. |
503 | emergency_shutdown | Org-wide emergency kill switch is engaged (see Governance Controls). |
HTTP status usage
| Code | Used for |
|---|---|
200 OK | Successful synchronous request returning a body. |
201 Created | Synchronous resource creation. Response includes the new record. |
202 Accepted | Asynchronous work queued. Response includes a polling identifier. |
204 No Content | Successful request with no body (typical for DELETE and some PATCH). |
3xx | Not used by the platform API. Public verifier URLs may issue 302 for short-link redirects. |
Asynchronous operations & polling
Long-running work — Guardian training, policy ingestion, scenario generation, training campaigns — accepts the request and responds 202 Accepted immediately:
{
"job_id": "job_01J...",
"status": "queued",
"poll_url": "/v1/training/jobs/job_01J..."
}
- Initial poll interval: 2 seconds.
- Backoff: linear up to 10 seconds, then steady.
- Terminal states:
ready/completed/failed/cancelled. Once terminal, the resource never changes state again — a retry creates a new job. - Failure detail lives on the resource itself (
error_message,failure_reason,ingestion_error), not in the polling envelope.
Rate limits
Authenticated endpoints are rate-limited per organization. Standard headers are emitted on every response:
| Header | Description |
|---|---|
X-RateLimit-Limit | Quota for the current window. |
X-RateLimit-Remaining | Requests remaining in the current window. |
X-RateLimit-Reset | Unix epoch seconds at which the window resets. |
Retry-After | Seconds to wait. Present on 429 responses only. |
Retry guidance:
- On
429, wait forRetry-Afterbefore retrying. The limiter is a hard bucket and will keep returning429until the window resets. - On
5xx, use exponential backoff with jitter starting at 1 s, capping at 30 s. - Idempotent retries are safe for
GET,PUT,DELETE, and anyPOSTcarrying anIdempotency-Keyheader.
Health and metrics endpoints are not rate-limited.
Pagination
List endpoints use cursor-based pagination by default:
| Param | Default | Description |
|---|---|---|
limit | 25 | Page size. Maximum is endpoint-specific (typically 100; 500 on some log surfaces). |
cursor | — | Opaque token from the previous response's next_cursor. Omit for the first page. |
{
"data": [ /* items */ ],
"page": {
"next_cursor": "string | null",
"has_more": true
}
}
A small number of legacy / audit-style endpoints use offset pagination (page + page_size); these are flagged on the endpoint page.
Filtering, sorting & time windows
| Param | Type | Description |
|---|---|---|
from / to | ISO-8601 timestamp | Time window. Inclusive of from, exclusive of to. Defaults to last 24 h where applicable. |
sort | string | Comma-separated field list. Prefix - for descending. |
q | string | Free-text search where supported. |
Surface-specific filters (status, guardian_id, verdict, risk_tier, …) are documented on the endpoint.
Idempotency
Mutating endpoints (POST, PATCH, DELETE) accept an idempotency key:
Idempotency-Key: <uuid-or-stable-string>
- On first call with a given key, the platform processes the request and stores the response.
- On a repeat call within 24 hours with the same key:
- If the body is byte-identical, the original response is returned (same status, same body).
- If the body differs,
409 idempotency_conflictis returned with the originalrequest_idindetails.
- After 24 hours, the key is forgotten.
GET requests are inherently idempotent and do not require the header.
Time, IDs & encodings
| Convention | Format | Example |
|---|---|---|
| Timestamps | RFC 3339 / ISO-8601 with timezone | 2026-04-26T15:42:00Z |
| Durations | ISO-8601 duration | PT30S, P1D |
| Resource IDs | Lowercase prefix + ULID-style suffix | gov_01J9X…, nhi_01J9X…, pol_01J9X… |
| Money | Decimal string in minor units | "12500" cents |
| Hashes | Lowercase hex | e3b0c4… |
| Binary content | Base64 (standard, not URL-safe) | inside a string field |
| Arrays | Always returned, never null | [] denotes empty |
Resource ID prefixes by surface:
| Prefix | Surface |
|---|---|
gov_ | Guardian |
pol_ | Policy document |
nhi_ | Non-human identity |
key_ | API key |
org_ | Organization |
usr_ | User |
role_ | Role |
ts_ | Test suite |
tr_ | Test run |
cmp_ | Training campaign |
job_ | Async job |
att_ | Attestation report |
evd_ | Evidence snapshot |
mcp_ | MCP server registration |
sk_ | Skill |
Versioning & deprecation
- Endpoints are versioned in the path (
/v1/…). Breaking changes ship under a new path prefix. - Additive changes — new optional request fields, new response fields, new enum values, new endpoints — do not trigger a path bump. Clients must ignore unknown response fields.
- Deprecated endpoints emit a
Deprecation: trueheader and aSunset:header (RFC 8594) with the planned removal date. - Removal lead time is at least 12 months from the first
Deprecationheader.
Standard headers
| Header | Direction | Purpose |
|---|---|---|
Authorization | Request | Session token or API key (see Authentication). |
X-Trinitite-Nhi-Token / X-Trinitite-Nhi-Id | Request | NHI principal headers when acting on behalf of an autonomous workload. |
X-Trinitite-Workload-Origin | Request | Required alongside any NHI header. |
Idempotency-Key | Request | See § Idempotency. |
X-Request-Id | Request | Client-supplied correlation ID; echoed back. If omitted, the platform assigns one. |
X-Request-Id | Response | Stable correlation ID for this request. |
X-RateLimit-* | Response | On rate-limited surfaces. |
ETag | Response | On versioned resources; use with If-Match for optimistic-concurrency PATCH. |
Deprecation / Sunset | Response | When applicable. |
Retry-After | Response | On 429 and select 503 responses. |
→ Authentication — principals, tokens, scopes, the API-key CRUD surface. → Verdict vocabulary — the three outcomes and receipt fields every govern call returns. → Webhooks — event delivery for verdicts and async job completion.