Error Codes
All API errors follow a consistent response format. This page documents every error code and its meaning.
Error Response Format
json
{
"success": false,
"data": null,
"error": {
"code": "ERROR_CODE",
"message": "Human-readable description",
"details": [
{ "field": "email", "issue": "Invalid email" }
],
"requestId": "req_abc123"
},
"meta": {
"timestamp": "2026-03-07T12:00:00.000Z"
}
}The details array is present only for VALIDATION_ERROR responses, listing each field that failed validation. The requestId is included when the request ID middleware has run.
Error Code Reference
| Code | HTTP Status | Description |
|---|---|---|
UNAUTHORIZED | 401 | Missing, invalid, or expired authentication token. Returned when no Authorization header is present or the token cannot be verified. |
AUTH_FAILED | 401 | Authentication credentials are invalid. Returned on wrong password, expired magic link, or failed OAuth exchange. |
VALIDATION_ERROR | 400 | Request body failed Zod schema validation. The details array lists each failing field and constraint. Also returned for duplicate resources (email already exists, slug already taken). |
NOT_FOUND | 404 | The requested resource does not exist. Returned for missing projects, API keys, workspaces, or users. |
FORBIDDEN | 403 | The authenticated user lacks permission for the requested operation. Returned when workspace role is insufficient (e.g., member attempting admin action) or API key scopes are insufficient. |
RATE_LIMITED | 429 | Request rate exceeded the configured limit. Check X-RateLimit-Reset header for when the window resets. |
INVALID_SOURCE | 400 | An invalid source value was specified in a query request. Must be "d1" or "kv". |
INTERNAL_ERROR | 500 | Unhandled error in the service layer. No internal details are exposed to the client. Check Worker logs for diagnostics. |
HTTP Status Code Summary
| Status | Meaning | When Returned |
|---|---|---|
| 200 | OK | Successful read, update, or delete |
| 201 | Created | Successful resource creation (register, create project, create key) |
| 202 | Accepted | Log entry queued for processing |
| 400 | Bad Request | Validation error, invalid source, bad slug format |
| 401 | Unauthorized | Missing or invalid auth, wrong credentials |
| 403 | Forbidden | Insufficient role or scope |
| 404 | Not Found | Resource does not exist |
| 409 | Conflict | Duplicate resource (email already registered, slug already taken) |
| 429 | Too Many Requests | Rate limit exceeded |
| 500 | Internal Server Error | Unhandled server error |
Context-Specific Error Codes
Authentication Errors
| Scenario | Code | Status | Message |
|---|---|---|---|
| No Authorization header | UNAUTHORIZED | 401 | Authentication required |
| Invalid JWT signature | UNAUTHORIZED | 401 | Invalid token |
| Expired JWT | UNAUTHORIZED | 401 | Token expired |
| Invalid API key | UNAUTHORIZED | 401 | Invalid or revoked API key |
| Wrong password | AUTH_FAILED | 401 | Invalid email or password |
| Expired magic link | AUTH_FAILED | 401 | Invalid or expired magic link |
| Email not verified | FORBIDDEN | 403 | Email verification required |
Authorization Errors
| Scenario | Code | Status | Message |
|---|---|---|---|
| Member attempts admin action | FORBIDDEN | 403 | Insufficient role |
| API key missing required scope | FORBIDDEN | 403 | Insufficient scope |
| Non-owner attempts ownership transfer | FORBIDDEN | 403 | Only owner can transfer |
| Unlinking last auth method | FORBIDDEN | 403 | Cannot remove last auth method |
Validation Errors
| Scenario | Code | Status | Message |
|---|---|---|---|
| Missing required field | VALIDATION_ERROR | 400 | Invalid input |
| Invalid email format | VALIDATION_ERROR | 400 | Invalid input |
| Password too short (<8 chars) | VALIDATION_ERROR | 400 | Invalid input |
| Duplicate email on register | VALIDATION_ERROR | 409 | User with this email already exists |
| Duplicate workspace slug | VALIDATION_ERROR | 409 | Slug already taken |
| Unsupported OAuth provider | VALIDATION_ERROR | 400 | Unsupported OAuth provider |
Security Properties
- Error messages are generic to prevent information leakage
- Stack traces are never exposed in production responses
forgot-passwordandmagic-linkalways return 200 to prevent user enumerationrequestIdin errors enables correlation with Worker logs for debugging
See Also
- API Reference -- Full endpoint documentation
- Rate Limits -- Rate limiting details