Error Handling
Understanding and handling API errors.
HTTP Status Codes
| Code | Meaning | Action |
|---|---|---|
200 | Success | Request completed |
201 | Created | Resource created |
400 | Bad Request | Check request format |
401 | Unauthorized | Token expired or invalid |
403 | Forbidden | Insufficient permissions |
404 | Not Found | Resource doesn't exist |
422 | Validation Error | Fix request data |
429 | Rate Limited | Slow down requests |
500 | Server Error | Retry or contact support |
Error Response Format
All errors return a consistent JSON structure:
{
"statusCode": 422,
"error": "Unprocessable Entity",
"message": "Validation failed",
"details": [
{
"field": "consumer.email",
"message": "\"email\" must be a valid email"
}
]
}
Handling Errors in Code
- TypeScript
interface ApiError {
statusCode: number;
error: string;
message: string;
details?: Array<{
field: string;
message: string;
}>;
}
async function createOrder(order: OrderPayload): Promise<Order> {
const response = await fetch("https://na1-prod.okcapsule.app/v2/orders", {
method: "POST",
headers: {
Authorization: `Bearer ${access_token}`,
"Content-Type": "application/json",
},
body: JSON.stringify(order),
});
if (!response.ok) {
const error: ApiError = await response.json();
switch (response.status) {
case 401:
// Token expired - refresh and retry
await refreshToken();
return createOrder(order);
case 422:
// Validation error - log details
console.error("Validation errors:", error.details);
throw new Error(`Validation failed: ${error.message}`);
case 429:
// Rate limited - wait and retry
const retryAfter = response.headers.get("Retry-After") || "60";
await sleep(parseInt(retryAfter) * 1000);
return createOrder(order);
default:
throw new Error(`API error: ${error.message}`);
}
}
const data = await response.json();
return data.order;
}
Common Validation Errors
Order Structure Errors
These are the most common errors when creating orders:
| Error | Cause | Fix |
|---|---|---|
"consumer" is required | Missing consumer object | Add consumer with either id or first_name/last_name |
"shipping_address" is required | Missing address object | Add shipping_address with required fields |
"order_lines" is required | Missing order lines | Add order_lines array with at least one item |
"order_lines" must contain at least 1 items | Empty order lines array | Include at least one order line |
"pouches" is required | Missing pouches in order line | Add pouches array to each order line |
Either pack_id or contents required | Pouch has neither | Add either pack_id OR contents (not both) |
"client_product_id" is required | Missing product in contents | Add client_product_id to each content item |
Consumer Errors
| Error | Cause | Fix |
|---|---|---|
"last_name" is required | Provided first_name without last_name | Include both first_name and last_name |
"id" must be a valid GUID | Invalid consumer ID format | Use valid UUID format |
Consumer not found | Consumer ID doesn't exist | Verify consumer exists with GET /v2/consumers/{id} |
"email" must be a valid email | Invalid email format | Check email syntax |
"phone_number" length must be less than or equal to 15 | Phone too long | Use E.164 format (+12125551234) |
Consumer Pattern
You must provide either:
consumer.id(existing consumer) ORconsumer.first_name+consumer.last_name(new consumer)
Don't mix both patterns.
Address Errors
| Error | Cause | Fix |
|---|---|---|
"address1" is required | Missing street address | Add address1 field |
"city" is required | Missing city | Add city field |
"country_name" is required | Missing country | Add country_name field |
Address line 1 must be less than 100 characters | Address too long | Shorten to 100 chars max |
City must be less than 50 characters | City name too long | Shorten to 50 chars max |
Product Errors
| Error | Cause | Fix |
|---|---|---|
"client_product_id" must be a valid GUID | Invalid product ID format | Use valid UUID format |
The following client products are inactive and cannot be ordered: [names] | Product is inactive | Use only products where active: true |
The following OKC products are inactive and cannot be ordered: [names] | Underlying OKC product discontinued | Contact OK Capsule support |
Product not found | Product ID doesn't exist | Verify product with GET /v2/products/{id} |
Product does not belong to client | Wrong client's product | Use products from your own catalog |
Packaging Asset Group Errors
| Error | Cause | Fix |
|---|---|---|
No Packaging Asset Group ID found for the order line | Missing PAG configuration | Contact OK Capsule to configure your packaging |
| Order set to "Needs Changes" status | PAG is not "Active" | Contact OK Capsule to approve your packaging design |
Packaging Asset Groups
Packaging Asset Groups (PAGs) define your packaging design. Orders require an active PAG, which is typically configured during client onboarding. If you see PAG errors, contact OK Capsule support.
Order Status Errors
| Error | Cause | Fix |
|---|---|---|
Order cannot be modified | Order status is past "Pending"/"On Hold" | Orders can only be modified before acceptance |
Order cannot be canceled | Order already in production | Contact support to cancel accepted orders |
Authentication Errors
401 Unauthorized
{
"statusCode": 401,
"error": "Unauthorized",
"message": "Token expired"
}
Solution: Refresh your access token:
async function refreshToken(): Promise<string> {
const response = await fetch(
"https://na1-prod.okcapsule.app/v2/authentication/refresh-token",
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ refresh_token }),
}
);
const data = await response.json();
return data.access_token;
}
403 Forbidden
{
"statusCode": 403,
"error": "Forbidden",
"message": "Insufficient permissions"
}
Solution: Your user lacks the required permission scope. Contact your admin to update role permissions.
Rate Limiting
When rate limited, the API returns:
HTTP/1.1 429 Too Many Requests
Retry-After: 60
Best practices:
- Implement exponential backoff
- Cache responses when possible
- Batch operations where supported
async function withRetry<T>(fn: () => Promise<T>, maxRetries = 3): Promise<T> {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await fn();
} catch (error) {
if (error.status === 429 && attempt < maxRetries - 1) {
const delay = Math.pow(2, attempt) * 1000; // 1s, 2s, 4s
await sleep(delay);
continue;
}
throw error;
}
}
throw new Error("Max retries exceeded");
}
Debugging Tips
- Check the full error response - Details array contains field-specific errors
- Validate before sending - Use the field constraints from Create Orders
- Test in Stage first - Use the stage environment for development
- Log request/response - Include correlation IDs for support tickets
Getting Help
If you encounter persistent errors:
- Check the Interactive API Docs for endpoint details
- Verify your request matches the expected schema
- Contact OK Capsule support with:
- Request URL and method
- Request body (redact sensitive data)
- Full error response
- Timestamp of the request