Error Reference
The API uses standard HTTP status codes. Error responses include a JSON body with a detail field describing the error. Some endpoints include structured error data for programmatic handling.
Error Response Format
All error responses return a JSON body with a single error object carrying a machine-readable code, a human-readable message, a broad category, help text, and the request ID for support correlation.
{
"error": {
"code": "MACHINE_READABLE_CODE",
"message": "Human-readable error description",
"category": "auth",
"help": "What to do about it.",
"request_id": "9f8e7d6c-...",
"details": {}
}
}Error Fields
| Name | Type | Description |
|---|---|---|
error.code* | string | Machine-readable error code for programmatic handling (e.g., "UNAUTHORIZED", "VALIDATION_ERROR", "RATE_LIMIT_EXCEEDED"). |
error.message* | string | Human-readable description of what went wrong. |
error.category* | string | Broad classification: auth, validation, rate_limit, permission, not_found, conflict, server, network, or config. |
error.help* | string | Actionable guidance for resolving the error. |
error.request_id | string | Request ID for support correlation. May be null. |
error.details* | object | Structured detail specific to the error (e.g. field-level validation errors). Empty object when not applicable. |
HTTP Status Codes
The ~alter API uses standard HTTP status codes to indicate success or failure. Successful requests return 2xx codes. Client errors return 4xx codes. Server errors return 5xx codes.
| Status | Code | Description |
|---|---|---|
400 | Bad Request | Invalid request body, parameters, or business rule violation |
401 | Unauthorized | Missing, expired, or invalid authentication credentials |
402 | Payment Required | x402 payment required for a paid tool or query |
403 | Forbidden | Authenticated but insufficient permissions or consent not granted |
404 | Not Found | Resource does not exist or is not accessible |
409 | Conflict | Resource already exists (duplicate email, already claimed stub, etc.) |
422 | Unprocessable Entity | Request body validation failed |
429 | Too Many Requests | Rate limit exceeded - check Retry-After header |
500 | Internal Server Error | Unexpected server error - contact support |
503 | Service Unavailable | Dependency unavailable (database, Redis, signing keys) |
Common Error Examples
Validation error (422)
Returned when the request body fails Pydantic validation. The error.details.errors array carries one entry per failing field.
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Request validation failed",
"category": "validation",
"help": "Check the highlighted fields and correct any errors.",
"request_id": "9f8e7d6c-...",
"details": {
"errors": [
{
"field": "body.email",
"message": "value is not a valid email address",
"type": "value_error.email"
}
]
}
}
}Authentication error (401)
{
"error": {
"code": "UNAUTHORIZED",
"message": "Could not validate credentials",
"category": "auth",
"help": "You need to sign in to access this.",
"request_id": "9f8e7d6c-...",
"details": {}
}
}Permission error (403)
{
"error": {
"code": "FORBIDDEN",
"message": "Consent not granted for trait profile access",
"category": "permission",
"help": "You don't have permission for this action.",
"request_id": "9f8e7d6c-...",
"details": {}
}
}Conflict error (409)
{
"error": {
"code": "RESOURCE_CONFLICT",
"message": "An account with this email already exists",
"category": "conflict",
"help": "This record already exists. If you believe this is wrong, contact support.",
"request_id": "9f8e7d6c-...",
"details": {}
}
}Rate Limiting
Rate limiting uses a Redis-backed sliding window counter and is applied per authenticated role, not by a free/pro tier split. A member or org admin gets 120 requests/minute and 5,000/day; org viewers and auditors get 60/minute and 2,000/day; unauthenticated requests run in the lowest bucket at 40/minute and 20,000/day. When a limit is exceeded, the API returns 429 Too Many Requests with a Retry-After header.
Rate Limit Headers
| Name | Type | Description |
|---|---|---|
X-RateLimit-Limit | string | Maximum requests allowed in the window |
X-RateLimit-Remaining | string | Requests remaining in the current window |
X-RateLimit-Reset | string | Unix timestamp when the window resets |
Retry-After | string | Seconds until the next request is allowed (only on 429) |
Rate limit exceeded response (429)
HTTP/1.1 429 Too Many Requests
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1709856000
Retry-After: 42
Content-Type: application/json
{
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Rate limit exceeded. Try again in 42 seconds.",
"category": "rate_limit",
"help": "You're making requests too quickly. Wait a moment and try again.",
"request_id": "9f8e7d6c-...",
"details": {}
}
}Respect the Retry-After header and implement exponential back-off for retries.
402 Payment Required
Paid MCP tools and credential queries return 402 Payment Required when invoked without a valid x402 payment proof. The response includes pricing metadata so the client can construct a payment.
{
"detail": "Payment required for paid tool access",
"code": "PAYMENT_REQUIRED",
"pricing": {
"amount": "0.30",
"currency": "USD",
"protocol": "x402",
"recipient": "alter_treasury",
"payment_url": "https://mcp.truealter.com/api/v1/payments/create",
"tool": "query_trait_profile",
"description": "Trait profile query - 75% accrues to the data subject; indicative only, server-authoritative at settlement"
}
}Free allowance: Each identity record carries a one-time free allowance, sized to its engagement level. A free read still earns the queried member recognition, a settled $0 signal toward how legible they are, but not Identity Income. Once the allowance is spent, x402 payment is required, and only paid reads earn income.
JSON-RPC Error Codes (MCP)
The MCP Identity Server uses JSON-RPC 2.0 for communication. Errors follow the standard JSON-RPC error format with ~alter-specific application codes in the -32000 to -32002 range.
| Code | Description |
|---|---|
-32700 | Parse error - invalid JSON |
-32600 | Invalid request - missing required fields |
-32601 | Method not found - unknown MCP method |
-32602 | Invalid params - tool argument validation failed |
-32603 | Internal error - server-side failure |
-32000 | Payment required - x402 payment needed for a paid tool |
-32001 | Rate limit exceeded |
-32002 | Authentication required |
JSON-RPC error response format
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32000,
"message": "Payment required for paid tool access",
"data": {
"tool": "query_trait_profile",
"pricing": {
"amount": "0.30",
"currency": "USD",
"protocol": "x402"
}
}
}
}Error Handling Best Practices
Always check the status code
Do not assume a 200 response. Check for 2xx before parsing the response body as a success object.
Implement token refresh proactively
Rather than waiting for a 401 response, decode the JWT expiry and refresh the token before it expires. See Token Refresh for the rotation flow. This avoids failed requests in flight.
Respect rate limit headers
Read X-RateLimit-Remaining on every response. If approaching zero, slow down before hitting the 429 limit.
Handle 402 gracefully for MCP
If building an MCP client, parse the pricing metadata from payment-required errors and present payment options to the user or agent. See the x402 example flow for the complete negotiation sequence.
Retry on 503 with back-off
Service unavailable errors are typically transient. Retry with exponential back-off (1s, 2s, 4s) up to 3 attempts.