Docs Error Handling

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, or x-api-timestamp headers
  • 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 of priority: "urgent")
  • Invalid status transition

How to fix:

  • Check the details array 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-Remaining before making requests
  • Wait until the X-RateLimit-Reset timestamp 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 4xx errors (except 429). 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)