Skip to main content

Error Handling

When things go wrong with an API request, the Qarion API communicates the problem through standard HTTP status codes and a structured error response body. Understanding these error patterns is important for building integrations that degrade gracefully and provide useful feedback to users.

Response Format

Error responses follow a consistent structure. For most errors, the response body contains a single detail field with a human-readable description of the problem:

{
"detail": "Error message describing what went wrong"
}

For validation errors (HTTP 422), the detail field contains an array of objects, each identifying a specific field that failed validation, the location of the field in the request, and a description of the validation rule that was violated:

{
"detail": [
{
"loc": ["body", "name"],
"msg": "field required",
"type": "value_error.missing"
}
]
}

This structured format makes it straightforward to map validation failures to specific form fields in a user interface or to log detailed diagnostic information for debugging.

HTTP Status Codes

Success Codes

CodeMeaningWhen Used
200OKSuccessful GET, PATCH
201CreatedSuccessful POST (new resource)
204No ContentSuccessful DELETE

Client Errors

Client errors indicate a problem with the request itself — something the caller can fix by correcting their input, credentials, or permissions:

CodeMeaningCommon Causes
400Bad RequestInvalid JSON, malformed request
401UnauthorizedMissing/invalid API key
403ForbiddenValid auth but insufficient permissions
404Not FoundResource doesn't exist
409ConflictDuplicate slug, concurrent modification
422Unprocessable EntityValidation failed
429Too Many RequestsRate limit exceeded

Server Errors

Server errors indicate a problem on the API side. These are typically transient and can be retried:

CodeMeaningAction
500Internal Server ErrorRetry, contact support
502Bad GatewayRetry after delay
503Service UnavailableSystem maintenance, retry later

Common Errors

401 Unauthorized

{
"detail": "Could not validate credentials"
}

This error occurs when the Authorization header is missing entirely, when the API key or session token is invalid, or when a previously valid token has expired. To fix it, verify that your API key is correct and that the header is formatted as Authorization: Bearer YOUR_KEY.

403 Forbidden

{
"detail": "Not authorized to access this resource"
}

A 403 response means your credentials are valid, but you don't have permission to perform the requested action. Common causes include trying to access a space you're not a member of, attempting an action that exceeds your role (such as creating a product as a Viewer), or trying to access a product that requires a formal access request. Check your space membership and role, or submit an access request if needed.

404 Not Found

{
"detail": "Product not found"
}

The resource you requested doesn't exist — or at least isn't visible to your account. This can happen because the resource was deleted, because you're using the wrong ID or slug, or because the resource exists in a different space. Verify that the identifier is correct and that you're querying the right space.

409 Conflict

{
"detail": "A product with this slug already exists"
}

Conflict errors arise when a request would violate a uniqueness constraint — most commonly, attempting to create a product with a slug that's already in use. They can also occur during concurrent modifications. To resolve it, either use a unique value or fetch the existing resource and update it instead.

422 Unprocessable Entity

{
"detail": [
{
"loc": ["body", "email"],
"msg": "value is not a valid email address",
"type": "value_error.email"
}
]
}

Validation errors mean the request body was syntactically valid JSON but contained one or more fields that don't meet the API's validation rules. The loc field tells you exactly which field failed, and the msg field explains why. Common causes include missing required fields, invalid formats (such as a malformed email address), and values outside the allowed range.

Retry Strategy

Not all errors should be retried — client errors (4xx) generally indicate a problem that won't resolve itself, while server errors (5xx) and rate limit errors (429) are often transient. Implement exponential backoff for retryable errors to avoid overwhelming the API during periods of transient instability:

import time
import requests

def api_request_with_retry(url, headers, max_retries=3):
for attempt in range(max_retries):
response = requests.get(url, headers=headers)

if response.status_code == 429:
# Rate limited - wait and retry
retry_after = int(response.headers.get("Retry-After", 60))
time.sleep(retry_after)
continue

if response.status_code >= 500:
# Server error - exponential backoff
time.sleep(2 ** attempt)
continue

return response

raise Exception("Max retries exceeded")

The key idea behind exponential backoff is that each subsequent retry waits longer than the previous one (1 second, 2 seconds, 4 seconds, etc.), which gives the server time to recover without being hammered by repeated requests.

Rate Limiting

When you exceed the API's rate limits, you'll receive a 429 Too Many Requests response with a Retry-After header indicating how many seconds to wait:

HTTP/1.1 429 Too Many Requests
Retry-After: 60

The API also includes rate limit metadata in the headers of every response, so you can monitor your usage proactively and slow down before hitting the limit:

  • X-RateLimit-Limit: Max requests per window
  • X-RateLimit-Remaining: Remaining requests
  • X-RateLimit-Reset: When the window resets
  • Retry-After: Seconds to wait before retrying

Best Practices

Always check the HTTP status code before attempting to parse the response body. A successful response will have a 2xx status code; anything else should be handled as an error. Log the full error details — including the status code, the detail field, and any request context — so you can diagnose issues without having to reproduce them.

Implement retry logic for 429 and 5xx errors, but never retry 4xx errors automatically (except for 429), since they indicate a problem that the caller needs to fix. Validate your inputs before making requests to catch obvious issues (missing required fields, invalid formats) on the client side, which is faster and cheaper than a round trip to the API. Finally, handle network errors (timeouts, connection refused, DNS failures) separately from API errors, since they require different recovery strategies.

Example Error Handler

The following JavaScript example demonstrates a comprehensive error handling pattern that maps each status code to a typed exception:

async function apiCall(url, options) {
try {
const response = await fetch(url, options);

if (!response.ok) {
const error = await response.json();

switch (response.status) {
case 401:
throw new AuthError("Please re-authenticate");
case 403:
throw new PermissionError("Access denied");
case 404:
throw new NotFoundError("Resource not found");
case 422:
throw new ValidationError(error.detail);
case 429:
throw new RateLimitError("Too many requests");
default:
throw new ApiError(error.detail || "Unknown error");
}
}

return response.json();
} catch (e) {
if (e instanceof TypeError) {
throw new NetworkError("Network request failed");
}
throw e;
}
}