Error Codes
This reference covers the error formats, HTTP status codes, and GraphQL error codes you may encounter when using the HappyColis API.
GraphQL Error Format
The HappyColis API follows the GraphQL specification for error responses. Errors are returned in the errors array alongside any partial data. The HTTP status code is always 200 for GraphQL responses — check the errors array to detect failures.
{
"data": null,
"errors": [
{
"message": "Shipment not found",
"extensions": {
"code": "NOT_FOUND"
},
"path": ["shipment"]
}
]
}| Field | Description |
|---|---|
message | Human-readable description of the error |
extensions.code | Machine-readable error code (see below) |
path | The GraphQL field path where the error occurred |
HTTP Status Codes
| Code | When |
|---|---|
200 OK | All GraphQL responses, including those containing errors |
400 Bad Request | Malformed JSON body or invalid GraphQL syntax |
401 Unauthorized | Missing, expired, or invalid access token (returned by the auth middleware before GraphQL executes) |
429 Too Many Requests | Rate limit exceeded (see Rate Limiting) |
500 Internal Server Error | Unexpected server-side failure |
GraphQL Error Codes
UNAUTHORIZED
The request is missing a valid Authorization header or the token has expired.
Cause: No token provided, token signature invalid, or token past its exp claim.
Solution: Obtain a fresh access token (see Authentication). Implement proactive token refresh ~60 seconds before expiry.
{
"message": "Unauthorized",
"extensions": { "code": "UNAUTHORIZED" }
}FORBIDDEN / INSUFFICIENT_SCOPE
The token is valid but lacks the required OAuth scope for the requested operation.
Cause: The access token was issued without the scope needed by the mutation or query (e.g. calling shipmentCreate without create_shipments).
Solution: Re-authenticate with the correct scopes, or contact your administrator to update the client's allowed scopes.
{
"message": "Insufficient scope: create_shipments required",
"extensions": { "code": "INSUFFICIENT_SCOPE" }
}NOT_FOUND
The requested resource does not exist or is not accessible to the authenticated organization.
Cause: The ID passed does not correspond to any existing record, or the record belongs to a different organization.
Solution: Verify the resource ID. Ensure you are using the correct organization credentials.
{
"message": "Order not found",
"extensions": { "code": "NOT_FOUND" }
}VALIDATION_ERROR
Input data failed validation — a required field is missing, a value is out of range, or the format is incorrect.
Cause: Invalid input in a mutation argument (e.g. malformed UUID, negative quantity, missing required field).
Solution: Review the input against the field definitions in the relevant mutation documentation. Check message for specific field details.
{
"message": "deliveryOrderId is required",
"extensions": { "code": "VALIDATION_ERROR" }
}CONFLICT
The operation conflicts with the current state of the resource.
Cause: Attempting an invalid state transition (e.g. completing an already-completed shipment, opening a cancelled order), or a unique constraint violation.
Solution: Query the resource first to check its current state before mutating. Use webhooks to stay in sync with resource state.
{
"message": "Shipment is already in COMPLETED state",
"extensions": { "code": "CONFLICT" }
}RATE_LIMITED
Too many requests have been made in a short period.
Cause: Your application exceeded the API rate limit.
Solution: Implement exponential backoff and retry logic. See Rate Limiting for limits and best practices.
{
"message": "Too many requests. Please retry after 30 seconds.",
"extensions": { "code": "RATE_LIMITED" }
}INTERNAL_ERROR
An unexpected error occurred on the server side.
Cause: A bug or transient infrastructure issue in the HappyColis platform.
Solution: Retry the request with exponential backoff. If the error persists, contact HappyColis support with the request ID from the response headers.
{
"message": "An internal error occurred. Please try again.",
"extensions": { "code": "INTERNAL_ERROR" }
}Token Error Responses
Authentication errors are returned as HTTP 401 responses before GraphQL executes, with a JSON body (not the standard GraphQL error format):
Expired or Invalid Token
{
"statusCode": 401,
"message": "Unauthorized"
}Invalid Client Credentials
{
"statusCode": 401,
"message": "Invalid client ID and/or grant type is not allowed"
}Invalid Refresh Token
{
"statusCode": 401,
"message": "Invalid refresh token"
}Handling Errors in Code
Node.js — Check Both HTTP Status and GraphQL Errors
async function gqlRequest(accessToken, query, variables = {}) {
const response = await fetch('https://api-v3.happycolis.com/graphql', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${accessToken}`,
},
body: JSON.stringify({ query, variables }),
});
// Auth errors arrive as non-200 HTTP responses
if (response.status === 401) {
throw new Error('UNAUTHORIZED: Token expired or invalid');
}
if (response.status === 429) {
const retryAfter = response.headers.get('Retry-After') || '60';
throw new Error(`RATE_LIMITED: retry after ${retryAfter}s`);
}
const result = await response.json();
// GraphQL errors are in the errors array even when HTTP status is 200
if (result.errors && result.errors.length > 0) {
const err = result.errors[0];
const code = err.extensions?.code || 'UNKNOWN';
throw Object.assign(new Error(err.message), { code });
}
return result.data;
}
// Usage with error handling
try {
const data = await gqlRequest(accessToken, query, variables);
} catch (err) {
if (err.code === 'NOT_FOUND') {
console.warn('Resource does not exist');
} else if (err.code === 'CONFLICT') {
console.warn('State conflict — check current resource state');
} else if (err.code === 'INSUFFICIENT_SCOPE') {
console.error('Missing OAuth scope — update client permissions');
} else {
console.error('Unexpected error:', err.message);
}
}Python — Structured Error Handling
import requests
class HappyColisError(Exception):
def __init__(self, message, code=None):
super().__init__(message)
self.code = code
def gql_request(access_token, query, variables=None):
response = requests.post(
'https://api-v3.happycolis.com/graphql',
headers={
'Authorization': f'Bearer {access_token}',
'Content-Type': 'application/json',
},
json={'query': query, 'variables': variables or {}},
)
if response.status_code == 401:
raise HappyColisError('Token expired or invalid', code='UNAUTHORIZED')
if response.status_code == 429:
retry_after = response.headers.get('Retry-After', '60')
raise HappyColisError(f'Rate limited, retry after {retry_after}s', code='RATE_LIMITED')
result = response.json()
if 'errors' in result and result['errors']:
err = result['errors'][0]
code = err.get('extensions', {}).get('code', 'UNKNOWN')
raise HappyColisError(err['message'], code=code)
return result['data']