Qubiclo uses standard HTTP status codes and a consistent error response format. All errors include a machine-readable code and a human-readable message.
{
"error": {
"code": "VALIDATION_ERROR",
"message": "claim_amount must be a positive integer",
"field": "claim_amount",
"request_id": "req_8f3d9c2e1a7b"
}
}Always include the request_id when reporting issues — it links the request to server-side logs.
| Status | Code | Meaning |
|---|---|---|
| 200 | OK | Request succeeded |
| 201 | CREATED | Resource created successfully |
| 400 | BAD_REQUEST | Malformed request body or missing required fields |
| 401 | UNAUTHORIZED | Missing or invalid access token |
| 403 | FORBIDDEN | Token valid but lacks required scope |
| 404 | NOT_FOUND | Resource does not exist |
| 409 | CONFLICT | Duplicate claim_id or conflicting state |
| 422 | VALIDATION_ERROR | Request body failed schema validation |
| 429 | RATE_LIMITED | Too many requests — check Retry-After header |
| 500 | INTERNAL_ERROR | Server error — include request_id when reporting |
VALIDATION_ERROR422One or more request fields failed validation. The field property names the offending field.
CLAIM_DUPLICATE409A claim with this claim_id already exists for the tenant.
CLAIM_NOT_FOUND404The specified claim_id does not exist or belongs to another tenant.
TOKEN_EXPIRED401Access token has expired. Use /auth/refresh to obtain a new one.
INSUFFICIENT_SCOPE403The token does not include the required permission scope for this action.
PROVIDER_NOT_CONTRACTED422The provider_id is not under an active contract with this tenant.
RULEBOOK_NOT_ACTIVE409No active rulebook found for this tenant. Publish a rulebook first.
UPLOAD_VIRUS_DETECTED422ClamAV detected malware in the uploaded file. The upload has been rejected.
RATE_LIMITED429Tenant rate limit exceeded. Retry after the interval in the Retry-After header.
# Python — robust error handling
import requests
def submit_claim(token: str, payload: dict):
resp = requests.post(
"http://localhost:8000/api/v1/claims/intake",
json=payload,
headers={"Authorization": f"Bearer {token}"},
timeout=30,
)
if resp.status_code == 422:
err = resp.json()["error"]
raise ValueError(f"Validation failed on '{err.get('field')}': {err['message']}")
if resp.status_code == 401:
raise PermissionError("Token expired — refresh and retry")
if resp.status_code == 429:
retry_after = resp.headers.get("Retry-After", "60")
raise RuntimeError(f"Rate limited — retry after {retry_after}s")
resp.raise_for_status()
return resp.json()All endpoints are rate-limited per tenant. Limits are communicated via response headers.
# Response headers on every request
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 842
X-RateLimit-Reset: 1716200400
# When limit is exceeded (HTTP 429)
{
"error": {
"code": "RATE_LIMITED",
"message": "Rate limit exceeded. Reset in 58 seconds.",
"request_id": "req_abc123"
}
}For POST requests, pass an Idempotency-Key header to safely retry without duplicate submissions. Keys are valid for 24 hours.
curl -X POST http://localhost:8000/api/v1/claims/intake \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{ "claim_id": "CLM-2026-1001", ... }'