Error Handling
The Ditio API uses standard HTTP status codes. This page covers common errors and how to handle them.
HTTP status codes
Section titled “HTTP status codes”| Status | Meaning | What to do |
|---|---|---|
200 OK | Request succeeded | Process the response data |
201 Created | Resource created successfully | Applies to POST endpoints |
204 No Content | Success, no response body | Applies to DELETE and some PATCH endpoints |
400 Bad Request | Invalid request parameters or validation error | Check your request body or query parameters |
401 Unauthorized | Missing or expired token | Refresh your access token — see Authentication |
403 Forbidden | Valid token but insufficient permissions | Check the scope on your token and whether the resource belongs to your company |
404 Not Found | Endpoint or resource doesn’t exist | Check the URL and resource ID |
429 Too Many Requests | Rate limit exceeded | Wait and retry with exponential backoff |
500 Internal Server Error | Server-side error | Retry after a short delay. Contact support if persistent |
Error response formats
Section titled “Error response formats”Simple errors include a message explaining what went wrong:
{ "error": "ArgumentIsNull", "message": "The 'ModifiedSince' parameter is required."}Validation errors from the v5 Integration API carry a structured errors
array:
{ "message": "Validation failed", "errors": [ { "category": "UserInvalidInputError", "code": "ValidationError", "field": "Phone", "message": "The Phone field is required." } ]}Common errors
Section titled “Common errors”401 Unauthorized — token expired
Section titled “401 Unauthorized — token expired”Access tokens are short-lived (about 30 minutes by default). When one expires, request a new token and retry:
if response.status_code == 401: token = refresh_access_token() response = requests.get(url, headers={"Authorization": f"Bearer {token}"})403 Forbidden — wrong scope or wrong company
Section titled “403 Forbidden — wrong scope or wrong company”A valid token can still be rejected if it lacks the scope the endpoint
requires (ditioapiv3, reportingapiv1, or masstransportapiv1 — see
Scopes), or if the requested
project/company doesn’t belong to your company.
400 Bad Request — deletion blocked
Section titled “400 Bad Request — deletion blocked”Integration API DELETE endpoints refuse to delete resources with dependent
data (e.g. a project with time registrations). Deactivate instead
(active: false), or use the is-*-deletable / deletable check endpoints
first.
400 Bad Request — invalid incremental-sync window
Section titled “400 Bad Request — invalid incremental-sync window”Extraction endpoints validate the sync window: ModifiedBefore requires
ModifiedSince and must be strictly after it.
Retry strategy
Section titled “Retry strategy”For transient errors (429, 500, 502, 503, 504), implement
exponential backoff:
- Wait 1 second, retry
- Wait 2 seconds, retry
- Wait 4 seconds, retry
- Wait 8 seconds, retry
- Give up and log the error
import timeimport requests
def api_call_with_retry(url, headers, max_retries=4): for attempt in range(max_retries + 1): response = requests.get(url, headers=headers) if response.status_code in (429, 500, 502, 503, 504): if attempt < max_retries: time.sleep(2 ** attempt) continue return response return responseDon’t retry 400/404 — those indicate a problem with the request itself.
Related
Section titled “Related”- Authentication — scopes and token refresh
- Pagination — resuming a paginated pull after an error