Skip to main content

Errors & Rate Limits

Error Response Format

All API errors follow a consistent format:
{
  "success": false,
  "error": {
    "code": "ERROR_CODE",
    "message": "Human-readable error description"
  }
}

Error Codes

Authentication Errors (4xx)

CodeHTTPDescriptionResolution
UNAUTHORIZED401Missing or invalid tokenInclude valid JWT in Authorization header
TOKEN_EXPIRED401JWT has expiredCall /auth/refresh-token with refresh token
INVALID_CREDENTIALS401Wrong email/passwordCheck credentials
FORBIDDEN403No permission for this actionVerify actor role and membership status
NO_MEMBERSHIP403No active membership in target centerRequest membership or switch to authorized center
MEMBERSHIP_SUSPENDED403Membership is suspendedContact center admin
MEMBERSHIP_ENDED403Membership has endedRe-apply for membership
INSUFFICIENT_ROLE403Role lacks required permissionADMIN, INSTRUCTOR, PARENT, CHILD hierarchy

Tenant Errors

CodeHTTPDescriptionResolution
CENTER_NOT_FOUND404Center domain does not existVerify center domain slug
CENTER_SUSPENDED403Center is temporarily suspendedContact partner admin
SUBDOMAIN_TAKEN409Subdomain already registeredChoose a different subdomain
PROVISIONING_FAILED500Center creation failedCheck provisioning logs, retry
DUPLICATE_PROVISIONING409Idempotent provisioning (already created)Use existing center from response

DPU Errors

CodeHTTPDescriptionResolution
DPU_NOT_FOUND404DPU record does not existVerify DPU ID
DPU_SEALED403Cannot modify AUDIT_READY recordCreate new DPU instead
CHAIN_BROKEN422Hash chain integrity violation detectedInvestigation required — data may be tampered
INVALID_EVIDENCE_LEVEL400Invalid evidence level transitionLevels can only increase: DRAFT → DOCUMENTED → AUDIT_READY

Validation Errors

CodeHTTPDescriptionResolution
VALIDATION_ERROR400Request body validation failedCheck required fields and types
INVALID_EMAIL400Email format is invalidUse valid email format
INVALID_SUBDOMAIN400Subdomain format invalid3-30 chars, lowercase alphanumeric + hyphens
MISSING_REQUIRED_FIELD400Required field not providedInclude all required fields

System Errors

CodeHTTPDescriptionResolution
INTERNAL_ERROR500Unexpected server errorRetry after 1 second, report if persistent
SERVICE_UNAVAILABLE503Service temporarily unavailableRetry with exponential backoff
DATABASE_ERROR500Database connection or query errorRetry, check service status

Rate Limiting

Limits by Tier

TierLimitWindowWho
Standard100 requestsper minuteRegular authenticated users
Partner500 requestsper minutePartner admin/operator accounts
Auth5 attemptsper 15 minutesLogin/password endpoints
Public30 requestsper minuteUnauthenticated endpoints

Rate Limit Headers

Every response includes rate limit information:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1709900400
HeaderDescription
X-RateLimit-LimitMaximum requests allowed in the window
X-RateLimit-RemainingRequests remaining in current window
X-RateLimit-ResetUnix timestamp when the window resets

When Rate Limited

{
  "success": false,
  "error": {
    "code": "RATE_LIMITED",
    "message": "Too many requests. Try again in 45 seconds."
  }
}
HTTP Status: 429 Too Many Requests Best practice: Implement exponential backoff:
async function fetchWithRetry(url: string, retries = 3) {
  for (let i = 0; i < retries; i++) {
    const res = await fetch(url, { headers });
    if (res.status !== 429) return res;

    const resetAt = parseInt(res.headers.get('X-RateLimit-Reset') || '0');
    const waitMs = Math.max((resetAt * 1000) - Date.now(), 1000 * (i + 1));
    await new Promise(r => setTimeout(r, waitMs));
  }
  throw new Error('Rate limit exceeded after retries');
}

Reliability

Uptime

ServiceTargetMonitoring
OPS API99.9%Internal CloudWatch
LMS API99.9%Internal CloudWatch
Auth (SSO)99.95%Critical path monitoring
DPU Engine99.99%Append-only, no data loss

Retry Strategy

For transient errors (5xx, network timeouts):
Attempt 1: immediate
Attempt 2: wait 1 second
Attempt 3: wait 2 seconds
Attempt 4: wait 4 seconds (max)
Do NOT retry 4xx errors automatically. These indicate client-side issues (bad request, auth failure, permission denied) that won’t resolve with retries.

Data Durability

ComponentGuarantee
PostgreSQL (RDS)Multi-AZ, automated backups, point-in-time recovery
DPU RecordsAppend-only, hash chain integrity, no delete/update after sealing
S3 Assets99.999999999% durability (11 nines)
RedisIn-memory cache, not persistence layer — data can be rebuilt

Idempotency

Provisioning endpoints support idempotent requests:
# Same orderId = same result (no duplicate centers)
POST /api/provisioning/center-tenant
{
  "orderId": "order_abc123",  # idempotency key
  "centerName": "New Center",
  "subdomain": "new-center",
  "planId": "plan_starter"
}
If the same orderId is sent twice, the API returns the existing center instead of creating a duplicate.