Error Handling
All API errors return a consistent JSON structure with a machine-readable code and human-readable message.
Error format
{
"error": {
"code": "auth.unauthorized",
"message": "Invalid or missing API key"
}
}Error codes
| Status | Code | Meaning |
|---|---|---|
| 401 | auth.unauthorized | Missing or invalid API key |
| 403 | auth.forbidden | Key is valid but lacks the scope, the IP is not allowlisted, or a plan limit or quota was hit |
| 404 | not_found | Resource does not exist |
| 409 | conflict | Duplicate or conflicting resource |
| 422 | validation.failed | Request body failed schema validation |
| 429 | rate_limit.exceeded | Too many requests |
| 500 | internal | Server error |
SDK error handling
import { HelmdeskError } from '@helmdesk/sdk'
try {
await client.tickets.get('invalid-id')
} catch (err) {
if (err instanceof HelmdeskError) {
console.log(err.status) // 404
console.log(err.code) // "not_found"
console.log(err.message) // "Ticket not found"
}
}Validation errors
A 422 adds a details object keyed by field name. Each value is an array — one field can fail more than one rule:
{
"error": {
"code": "validation.failed",
"message": "Validation failed",
"details": {
"subject": ["Required"],
"customerEmail": ["Invalid email address"]
}
}
}details is only present on 422 responses. Other errors carry just code and message.
Rate limits and quotas
Two different limits produce two different codes, and they need different handling:
| Limit | Response | What to do |
|---|---|---|
| 100 requests/min per key | 429 rate_limit.exceeded | Back off and retry — it clears within the minute. |
| Plan quota (emails, log events, storage) | 403 auth.forbidden | Retrying will not help. The message names the limit; upgrade or wait for the monthly reset. |
Warnings are not errors
A few endpoints return 200 with a warnings array — the operation succeeded, but something is worth knowing. Template import is the main one: an unresolved layout or partial warns rather than failing, so an email can send unbranded while the request looks entirely healthy. Check warnings when it is present.