Authentication

All API requests must be authenticated using API keys. Include your API key in the request header to access protected endpoints.

API Key Authentication

Include your API key in the X-API-Key header with every request:
Authentication Header
X-API-Key: ucos_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Keys have the form ucos_<live|test>_<32 base64url characters>, for example ucos_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX. Keep them server-side and rotate any key that appears in a log or a screenshot.

Generating API Keys

Before you can make API requests, you need to generate an API key from your app's settings. Follow these steps to create your first API key:
  1. Log in to your Ultimate Custom Order Status dashboard
  2. Navigate to Settings > API Keys
  3. Click "Generate New API Key"
  4. Configure the permissions for your API key
  5. Save the API key securely (it will only be shown once)

💡 Important: Store your API key in a secure location immediately after generation. For security reasons, the key will only be displayed once and cannot be retrieved later. If you lose your API key, you'll need to generate a new one.

Managing API Keys

A key’s display name and description can be edited in Settings > API Keys. Everything that affects authentication is fixed when the key is created.
  • The display name and description are editable at any time.
  • The key material, permissions, rate limits and expiry are fixed at creation and cannot be changed afterwards.
  • To change any of those, revoke the key and create a replacement. A revoked key stays listed and refuses authentication until its record is deleted.
  • Rate limits are set per key at creation (defaults: 60 requests per minute and 10,000 per day) and are reported in the X-RateLimit-* response headers.
  • The keys card lists the most recent requests each key served, so usage is auditable in the app.

API Key Permissions

Each API key can be configured with specific permissions. Only these five can be granted:
PermissionDescription
canViewOrdersRead access to orders
canChangeOrderStatusUpdate order status and sub-status
canSendNotificationsSend customer notifications (email, SMS, WhatsApp)
canViewStatusesRead access to custom statuses
canOverrideProductChainEnforcementBypass product chain enforcement rules when updating status

Rate Limiting

API requests are rate limited per API key using two separate windows:
  • Per-minute limit: Set per key at creation (default: 60 requests/minute)
  • Per-day limit: Set per key at creation (default: 10,000 requests/day)

Both limits must be satisfied for a request to be allowed. If either limit is exceeded, the request will be rejected with a 429 status code.

A request over either limit is rejected with 429, a Retry-After header, and details.retryAfter in the body.

Rate limit information is included in all API response headers:

  • X-RateLimit-Limit-Minute: Maximum requests per minute
  • X-RateLimit-Remaining-Minute: Remaining in current minute
  • X-RateLimit-Reset-Minute: Unix timestamp (seconds) when minute resets
  • X-RateLimit-Limit-Day: Maximum requests per day
  • X-RateLimit-Remaining-Day: Remaining in current day
  • X-RateLimit-Reset-Day: Unix timestamp (seconds) when day resets
  • Retry-After: Seconds to wait before retrying (present when rate limited)

Security Best Practices

⚠️ Important Security Guidelines

  • Never commit API keys to version control
  • Store API keys in environment variables or secure key management systems
  • Rotate API keys regularly
  • Use different API keys for different environments (development, staging, production)
  • Immediately revoke any API keys that may have been compromised
  • Use the minimum necessary permissions for each API key
  • Monitor API usage for unusual patterns

Audit Logging

Requests are recorded against the key that authenticated them, and rejected attempts are recorded separately for support.
  • Authenticated requests log the endpoint, method, status code, response time, source address and user agent.
  • A request that fails (4xx or 5xx) also records the request body and the response’s error message, so a rejected payload can be diagnosed from the log.
  • A request rejected before authentication (missing or unknown key, 401) has no per-key row; the app records the attempt separately (reason, last four characters of any key presented, source and endpoint) for support.
  • Both kinds of record are kept for 90 days by default and then pruned.
  • A revoked or expired key that is presented is recorded as a 401 (so a retired key still in use stays visible) but does not count as a use.
  • The key’s usage counter and last-used time update exactly once per authenticated request, including requests refused by a permission check or by the rate limit.

Example Request

Here's a complete example of an authenticated API request:
Authenticated cURL Request
curl -X GET "https://api.ultimate-custom-order-status.apps.msmtech.ca/api/v1/orders?page=1&limit=25" \
  -H "X-API-Key: ucos_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" \
  -H "X-API-Version: 1.5"

Error Responses

401 Unauthorized

Returned when the API key is missing or invalid:

401 Unauthorized
{
  "success": false,
  "error": "Missing API key. Include it in the X-API-Key header"
}

403 Forbidden

Returned when the API key lacks required permissions:

403 Forbidden
{
  "success": false,
  "error": "Insufficient permissions. Required: canViewOrders"
}

400 Bad Request

The request is invalid: a field is missing or has the wrong value, the JSON is malformed, an unsupported API version was requested, or a query value is invalid. Most validation failures carry details.errors; other 400s may carry different details (for example supported_versions for an unknown API version).

400 Bad Request
{
  "success": false,
  "error": "Validation failed",
  "details": {
    "errors": [
      {
        "code": "invalid_type",
        "expected": "string",
        "received": "undefined",
        "path": ["status_id"],
        "message": "Required"
      }
    ]
  }
}

405 Method Not Allowed

The route exists but does not accept this HTTP method. The HTTP framework produces this response before the API runs, so it does not use the { success, error } envelope.

429 Too Many Requests

The key's per-minute or per-day limit was reached. Wait details.retryAfter seconds (also sent in the Retry-After header) before retrying.

429 Too Many Requests
{
  "success": false,
  "error": "Rate limit exceeded",
  "details": {
    "retryAfter": 30
  }
}

500 Internal Server Error

An unexpected error occurred. details.message may contain diagnostics; retry with backoff.

500 Internal Server Error
{
  "success": false,
  "error": "Internal server error",
  "details": {
    "message": "An unexpected error occurred"
  }
}