Authentication
Trinitite authenticates every authenticated request with a single header:
Authorization: Bearer <session_token | api_key>
The platform recognises three classes of principal: human users (session token), machine integrators (API key), and autonomous workloads (Non-Human Identities, presented alongside an API key). Every record returned by the API is filtered to the caller's organization at the data layer — cross-organization access is impossible.
Principals
| Principal | Carrier | Use case |
|---|---|---|
| Human user | Session token | Browser dashboard / first-party UI |
| Machine integrator | API key | Backend service, CI pipeline, CLI tool |
| Non-Human Identity (NHI) | JIT token + workload origin (alongside an API key) | Autonomous AI agent, federated workload |
A single request may carry at most one of session token or API key. NHI credentials are sent as additional headers.
Session tokens
Session tokens are issued by the auth lifecycle endpoints. They are short-lived, refreshable, and individually revocable.
| Method | Path | Purpose |
|---|---|---|
POST | /auth/register | Register a new user (and organization, if standalone) |
POST | /auth/login | Issue a session token from credentials |
POST | /auth/refresh | Refresh an expiring session |
POST | /auth/logout | Revoke the current session |
GET | /auth/me | Return the authenticated user, organization, and roles |
POST | /auth/send-verification-email | Send (or resend) the email verification link |
GET | /auth/verify-email?token=... | Verify an email address |
GET | /auth/verification-status | Check the current user's verification state |
Login
curl -X POST https://api.trinitite.ai/auth/login \
-H "Content-Type: application/json" \
-d '{ "email": "ops@acme.com", "password": "..." }'
{
"sessionToken": "sess_01JF8R3M3X4N5Q6T7V8W9Y0Z1A...",
"user": {
"userId": "usr_01JF8...",
"email": "ops@acme.com",
"displayName": "Acme Ops"
},
"organization": {
"organizationId": "org_01JF8...",
"organizationName": "Acme"
},
"roles": ["developer"]
}
To list and revoke sessions, see the Sessions API.
User, role, and organization management endpoints (e.g. POST /v1/users, PATCH /v1/organization) accept only session tokens, not API keys. Programmatic principals cannot create users.
API keys
API keys are long-lived machine credentials bound to your organization, issued from a session and managed through the API key endpoints. Each key carries an explicit scope list matched against the permission required by every endpoint it touches.
Authorization: Bearer trit_live_4f8d9e2a1c6b7f3a9e1d2c4b5a6f7e8d
- Never commit API keys to version control.
- Never expose keys in client-side code.
- Use environment variables —
TRINITITE_API_KEY. - Rotate every 90 days, or immediately on suspected compromise.
Create an API key
POST /auth/api-keys — session-authenticated.
| Field | Type | Required | Description |
|---|---|---|---|
label | string | Yes | Human-readable label (e.g. production, ci-runner) |
scopes | string[] | Yes | Permission scopes (see § Permission scopes) |
expires_at | string (RFC 3339) | No | Optional explicit expiry |
curl -X POST https://api.trinitite.ai/auth/api-keys \
-H "Authorization: Bearer $TRINITITE_SESSION_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"label": "production-pii-redactor",
"scopes": ["guardians:read", "logs:read"]
}'
{
"key_id": "key_01JF8R3M3X4N5Q6T7V8W9Y0Z1A",
"label": "production-pii-redactor",
"scopes": ["guardians:read", "logs:read"],
"plaintext_key": "trit_live_4f8d9e2a1c6b7f3a9e1d2c4b5a6f7e8d",
"created_at": "2026-05-01T22:14:33Z",
"expires_at": null
}
plaintext_key is shown one time on creation. Store it in your secrets manager immediately. The platform stores only a hashed copy.
List API keys
GET /auth/api-keys — accepts session token or API key (with api_keys:read).
{
"data": [
{
"key_id": "key_01JF8...",
"label": "production-pii-redactor",
"prefix": "trit_live_4f8d",
"scopes": ["guardians:read", "logs:read"],
"status": "active",
"created_at": "2026-05-01T22:14:33Z",
"last_used_at": "2026-05-01T23:41:08Z",
"expires_at": null
}
],
"page": { "next_cursor": null, "has_more": false }
}
Inspect effective features
GET /auth/api-keys/{keyId}/effective-features
Returns the entitlements available to a specific key (computed from organization plan + key scopes). Use this when debugging unexpected 403 entitlement_denied responses.
Revoke an API key
DELETE /auth/api-keys/{keyId} — session-authenticated.
Revoked keys immediately return 401 unauthenticated on every subsequent request. Revocation is permanent — generate a new key to replace it.
{ "message": "API key revoked", "key_id": "key_01JF8...", "revoked_at": "2026-05-01T23:55:00Z" }
Permission scopes
Permissions are strings of the form resource:action. An endpoint declares the permission it requires; the platform compares it against the caller's scopes.
| Permission | Grants |
|---|---|
guardians:read | List, view, and call Guardians |
guardians:create | Create Guardians |
guardians:write | Update / soft-delete Guardians |
guardians:admin | Power operations (e.g. force-ready) |
logs:read | Read the Glass Box Ledger |
policies:read / policies:write | Policy documents and their nodes |
mcp:read / mcp:write | MCP gateway servers, bindings, OAuth |
nhi:read / nhi:write | Non-Human Identities |
compliance:read / compliance:write | Compliance frameworks, evidence, DSR |
reports:read / reports:write | Enterprise reports |
skills:read / skills:write | Skill Vault |
cli:read / cli:create | CLI Firewall (read; execute) |
api_keys:read / api_keys:write | API key inventory |
audit_logs:read | Auditor activity / governance audit log |
Two wildcard forms are recognised:
<resource>:*— all actions on a single resource (e.g.guardians:*).*or*:*— all actions on all resources. Reserved for the built-inadminsystem role and explicitly elevated keys.
System roles ship pre-defined and immutable: admin, auditor, developer, viewer. See Roles API for the complete catalogue and how to define custom roles.
Non-Human Identity (NHI) headers
Autonomous workloads — AI agents, federated identities, MCP clients — present an NHI alongside their API key. Two header pairs are accepted:
X-Trinitite-Nhi-Token: <jit_token>
X-Trinitite-Workload-Origin: <origin_descriptor>
or, for re-use of a previously issued NHI:
X-Trinitite-Nhi-Id: <nhi_id>
X-Trinitite-Workload-Origin: <origin_descriptor>
The platform validates the JIT token, confirms the workload origin matches the NHI's registered origin, attaches the NHI's privilege tier to the request context, and enforces tier-based filtering on the response. Issue NHI tokens via POST /v1/proxy/nhi/tokens — see the Identities reference.
When proxying upstream LLM traffic, also include X-Trinitite-Credential-Id to identify the vaulted provider credential — see Provider Credentials.
Organization scope
The organization is always derived from the credential. Cross-organization requests are not possible: every record returned by the API is filtered to the caller's organization at the data layer. Multi-tenant deployments are isolated at the row level. Any organizationId field in a request body is ignored.
Unauthenticated endpoints
The following endpoints accept no authentication and are safe for public exposure:
| Endpoint | Purpose |
|---|---|
GET /metrics | Prometheus exposition |
GET /health / /health/liveness / /health/readiness | Operational probes |
GET /.well-known/jwks.json | Public-key bundle for verifying issued artifacts |
GET /.well-known/oauth-protected-resource | OAuth 2.1 metadata for the MCP Gateway |
GET /v1/public/jwks | Alias of the JWKS endpoint |
GET /v1/public/verify/* | Stateless attestation-bundle verification |
GET /v1/public/attestations/{public_id} | Operator-published attestation lookup |
See Public Verification for full details on the verifier surface.
Standard request headers
| Header | Required | Purpose |
|---|---|---|
Authorization | Conditional | Session token or API key |
X-Trinitite-Nhi-Token / X-Trinitite-Nhi-Id | Conditional | NHI principal headers |
X-Trinitite-Workload-Origin | Conditional | Required alongside any NHI header |
X-Trinitite-Credential-Id | Conditional | Required on /v1/proxy/* |
Idempotency-Key | Optional | See § Idempotency |
Content-Type | POST / PATCH | application/json unless documented otherwise |
Accept | Optional | application/json default; some exports accept application/pdf, text/csv, application/oscal+json |
X-Request-Id | Optional | Client correlation ID; echoed back |
Standard response headers
| Header | Always | Purpose |
|---|---|---|
X-Request-Id | Yes | Stable correlation ID; matches error.request_id |
X-RateLimit-Limit | On rate-limited surfaces | Quota for the current window |
X-RateLimit-Remaining | On rate-limited surfaces | Requests remaining in the current window |
X-RateLimit-Reset | On rate-limited surfaces | Unix epoch seconds at which the window resets |
Retry-After | On 429 and select 503 | Seconds to wait before retrying |
ETag | On versioned resources | Use with If-Match for optimistic-concurrency PATCH |
Deprecation / Sunset | When applicable | RFC 8594 deprecation signalling |
Error envelope
Every error response — regardless of HTTP status — uses a single JSON shape:
{
"error": {
"code": "string",
"message": "human readable description",
"details": { },
"request_id": "req_01J9X..."
}
}
| Field | Description |
|---|---|
error.code | Stable machine-readable code (snake_case). Safe to switch on |
error.message | Human-readable description; may vary across releases |
error.details | Endpoint-specific structured context (validation paths, missing scopes, conflict markers) |
error.request_id | Correlates with X-Request-Id and the audit log |
Common codes
| HTTP | error.code | Meaning |
|---|---|---|
400 | validation_error | Body or query failed schema validation. details lists field paths |
400 | bad_request | Semantic precondition violated |
401 | unauthenticated | Missing or invalid credential |
403 | forbidden | Authenticated but lacking the required permission. details.required_permission set |
403 | entitlement_denied | Caller's organization is missing a required entitlement |
404 | not_found | Resource does not exist or is outside caller's organization |
409 | conflict | Resource state precludes the requested transition |
409 | idempotency_conflict | Idempotency key reused with a different request body |
410 | resource_gone | Hard-deleted resource |
422 | unprocessable_entity | Domain-level validation failed |
429 | rate_limited | Per-organization rate limit exceeded |
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 |
Rate limits
Authenticated endpoints are rate-limited per organization. Health and metrics endpoints are exempt. Read the headers on every response:
X-RateLimit-Limit: 500
X-RateLimit-Remaining: 487
X-RateLimit-Reset: 1746139200
On 429:
{
"error": {
"code": "rate_limited",
"message": "Rate limit exceeded. Retry after 60 seconds.",
"request_id": "req_01J..."
}
}
Retry-After: 60
Retry guidance:
- On
429, wait forRetry-Afterbefore retrying. Do not retry sooner — the limiter is a hard bucket. - 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.
Idempotency
Mutating endpoints (POST, PATCH, DELETE) accept an idempotency key:
Idempotency-Key: <uuid-or-stable-string>
- First call with a given key: the platform processes the request and stores the response.
- Repeat call within 24 hours, byte-identical body: the original response is returned (same status, same body).
- Repeat call within 24 hours, different body:
409 idempotency_conflictwith the originalrequest_idindetails. - After 24 hours the key is forgotten.
GET requests are inherently idempotent and do not require the header.
Pagination
List endpoints use cursor-based pagination by default.
GET /v1/logs?limit=100&cursor=<opaque>
{
"data": [ /* items */ ],
"page": {
"next_cursor": "string | null",
"has_more": true
}
}
A small set of legacy endpoints (notably GET /v1/guardians, GET /v1/users, GET /v1/roles) use offset pagination (limit + offset). Each endpoint's reference page documents which form it uses.
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
Deprecation: trueandSunset: <RFC 1123 date>headers (RFC 8594). - Removal lead time is at least 12 months from the first
Deprecationheader.
Verify your credential
- cURL
- Python
- JavaScript
curl -X GET https://api.trinitite.ai/v1/guardians \
-H "Authorization: Bearer $TRINITITE_API_KEY"
import os, requests
response = requests.get(
"https://api.trinitite.ai/v1/guardians",
headers={"Authorization": f"Bearer {os.environ['TRINITITE_API_KEY']}"},
)
const response = await fetch('https://api.trinitite.ai/v1/guardians', {
headers: { Authorization: `Bearer ${process.env.TRINITITE_API_KEY}` },
});
A 200 OK response confirms the credential is live. A 401 unauthenticated means the credential is missing, malformed, expired, or revoked. A 403 forbidden means the credential is valid but lacks guardians:read.
Next steps
- Drop your first call → Chat endpoint
- Manage human users → Users API
- Define custom roles → Roles API
- Issue identities for autonomous agents → Identities