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
X-API-Key: ucos_live_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXKeys 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
- Log in to your Ultimate Custom Order Status dashboard
- Navigate to Settings > API Keys
- Click "Generate New API Key"
- Configure the permissions for your API key
- 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
- 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
| Permission | Description |
|---|---|
canViewOrders | Read access to orders |
canChangeOrderStatus | Update order status and sub-status |
canSendNotifications | Send customer notifications (email, SMS, WhatsApp) |
canViewStatuses | Read access to custom statuses |
canOverrideProductChainEnforcement | Bypass product chain enforcement rules when updating status |
Rate Limiting
- 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 minuteX-RateLimit-Remaining-Minute: Remaining in current minuteX-RateLimit-Reset-Minute: Unix timestamp (seconds) when minute resetsX-RateLimit-Limit-Day: Maximum requests per dayX-RateLimit-Remaining-Day: Remaining in current dayX-RateLimit-Reset-Day: Unix timestamp (seconds) when day resetsRetry-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
- 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
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:
{
"success": false,
"error": "Missing API key. Include it in the X-API-Key header"
}403 Forbidden
Returned when the API key lacks required permissions:
{
"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).
{
"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.
{
"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.
{
"success": false,
"error": "Internal server error",
"details": {
"message": "An unexpected error occurred"
}
}