Error Handling
Tiqora uses conventional HTTP status codes to indicate the success or failure of API requests. Codes in the 2xx range indicate success, 4xx codes indicate client errors, and 5xx codes indicate server errors.
Error Response Format
All errors follow a consistent JSON structure:
{
"error": {
"code": "ERROR_CODE",
"message": "Human-readable description of what went wrong.",
"details": []
}
}
| Field | Type | Description |
|---|---|---|
code |
string | Machine-readable error code (use for programmatic handling) |
message |
string | Human-readable description |
details |
array | Additional context (present for validation errors) |
Error Codes Reference
401 — Unauthorized
{
"error": {
"code": "UNAUTHORIZED",
"message": "Authentication failed."
}
}
Common causes:
- Missing
x-api-key,x-api-signature, orx-api-timestampheaders - Invalid or revoked API key
- Incorrect HMAC signature (wrong secret, wrong signing algorithm)
- Timestamp older than 5 minutes (clock drift or replay attack)
How to fix:
- Verify all three authentication headers are present
- Check that your API key is active (not revoked or expired)
- Review your signature generation code against the Authentication guide
- Ensure your server clock is synchronized with NTP
403 — Forbidden
{
"error": {
"code": "FORBIDDEN",
"message": "Insufficient permissions."
}
}
Common causes:
- Your API key doesn't have permission for this operation
- Attempting to access a resource belonging to another tenant
How to fix:
- Check your API key's permission settings in the dashboard
- Ensure you're accessing resources within your own tenant
404 — Not Found
{
"error": {
"code": "NOT_FOUND",
"message": "Ticket not found."
}
}
Common causes:
- The resource ID doesn't exist
- The resource belongs to a different tenant (appears as not found for isolation)
- The resource has been deleted (soft-deleted resources return 404)
How to fix:
- Double-check the resource ID
- Ensure you're using the correct API key for the right tenant
409 — Conflict
{
"error": {
"code": "CONFLICT",
"message": "Cannot rotate an inactive or expired API key."
}
}
Common causes:
- Attempting to create a duplicate resource (e.g., customer with existing email)
- Invalid state transition (e.g., closing a ticket that's already closed)
- Resource is in a state that prevents the operation
413 — Payload Too Large
{
"error": {
"code": "PAYLOAD_TOO_LARGE",
"message": "File exceeds maximum size of 10 MB."
}
}
Limits:
- Max file size: 10 MB per file
- Max total attachments per ticket: 50 MB
- Supported formats: PNG, JPG, GIF, WebP, PDF, DOCX, XLSX, CSV, TXT, ZIP
422 — Validation Error
{
"error": {
"code": "VALIDATION_ERROR",
"message": "The given data was invalid.",
"details": [
{ "field": "subject", "message": "The subject field is required." },
{ "field": "priority", "message": "The selected priority is invalid." }
]
}
}
Common causes:
- Missing required fields
- Invalid field values (wrong type, out of range)
- Invalid enum values (e.g.,
priority: "critical"instead ofpriority: "urgent") - Invalid status transition
How to fix:
- Check the
detailsarray for specific field-level errors - Review the endpoint documentation for required fields and valid values
429 — Rate Limit Exceeded
{
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Too many requests. Please retry after 30 seconds."
}
}
Rate limit info is in the response headers:
X-RateLimit-Limit: 500
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1705312260
How to handle:
- Implement exponential backoff in your client
- Check
X-RateLimit-Remainingbefore making requests - Wait until the
X-RateLimit-Resettimestamp before retrying - Consider upgrading your plan for higher limits
500 — Internal Server Error
{
"error": {
"code": "INTERNAL_SERVER_ERROR",
"message": "An unexpected error occurred. Please try again."
}
}
How to handle:
- Retry the request after a short delay
- If persistent, contact Tiqora support with the request ID (if available)
- Server errors are logged on our end and investigated
Handling Errors in Code
JavaScript
async function createTicket(ticketData) {
const response = await tiqoraRequest('POST', '/api/v1/external/tickets', ticketData);
if (response.error) {
switch (response.error.code) {
case 'VALIDATION_ERROR':
// Show field-specific errors to the user
response.error.details.forEach(detail => {
console.error(`${detail.field}: ${detail.message}`);
});
break;
case 'RATE_LIMIT_EXCEEDED':
// Wait and retry
await new Promise(resolve => setTimeout(resolve, 30000));
return createTicket(ticketData);
case 'UNAUTHORIZED':
// Re-check credentials
console.error('Authentication failed. Check API key and secret.');
break;
default:
console.error(`Error: ${response.error.message}`);
}
return null;
}
return response.data;
}
Python
def create_ticket(ticket_data):
response = tiqora_request('POST', '/api/v1/external/tickets', ticket_data)
if 'error' in response:
error = response['error']
if error['code'] == 'VALIDATION_ERROR':
for detail in error.get('details', []):
print(f"Field '{detail['field']}': {detail['message']}")
elif error['code'] == 'RATE_LIMIT_EXCEEDED':
time.sleep(30)
return create_ticket(ticket_data)
else:
print(f"Error: {error['message']}")
return None
return response['data']
Retry Strategy
For transient errors (429, 500, 502, 503, 504), implement retry with exponential backoff:
async function requestWithRetry(method, path, body, maxRetries = 3) {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
const response = await tiqoraRequest(method, path, body);
if (!response.error) return response;
const retryableCodes = ['RATE_LIMIT_EXCEEDED', 'INTERNAL_SERVER_ERROR'];
if (!retryableCodes.includes(response.error.code) || attempt === maxRetries) {
throw new Error(response.error.message);
}
const delay = Math.min(1000 * Math.pow(2, attempt), 30000);
await new Promise(resolve => setTimeout(resolve, delay));
}
}
Hint: Never retry
4xxerrors (except429). They indicate a problem with your request that won't be fixed by retrying.
HTTP Status Code Summary
| Code | Meaning | Retryable? |
|---|---|---|
200 |
Success | — |
201 |
Created | — |
204 |
No Content (deleted) | — |
401 |
Unauthorized | No |
403 |
Forbidden | No |
404 |
Not Found | No |
409 |
Conflict | No |
413 |
Payload Too Large | No |
422 |
Validation Error | No |
429 |
Rate Limited | Yes (with backoff) |
500 |
Server Error | Yes (with backoff) |