Skip to content

Error Handling

The Ditio API uses standard HTTP status codes. This page covers common errors and how to handle them.

StatusMeaningWhat to do
200 OKRequest succeededProcess the response data
201 CreatedResource created successfullyApplies to POST endpoints
204 No ContentSuccess, no response bodyApplies to DELETE and some PATCH endpoints
400 Bad RequestInvalid request parameters or validation errorCheck your request body or query parameters
401 UnauthorizedMissing or expired tokenRefresh your access token — see Authentication
403 ForbiddenValid token but insufficient permissionsCheck the scope on your token and whether the resource belongs to your company
404 Not FoundEndpoint or resource doesn’t existCheck the URL and resource ID
429 Too Many RequestsRate limit exceededWait and retry with exponential backoff
500 Internal Server ErrorServer-side errorRetry after a short delay. Contact support if persistent

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."
}
]
}

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.

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.

For transient errors (429, 500, 502, 503, 504), implement exponential backoff:

  1. Wait 1 second, retry
  2. Wait 2 seconds, retry
  3. Wait 4 seconds, retry
  4. Wait 8 seconds, retry
  5. Give up and log the error
import time
import 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 response

Don’t retry 400/404 — those indicate a problem with the request itself.