Skip to content
novilDevelopersOpen Novil
Getting startedErrors

Errors

Every error response is an RFC 9457 problem-details document with the media type application/problem+json. On top of the standard fields, Novil adds a stable, machine-readable code. Branch on code and status; title and detail are for people and may be reworded.

Example: 403 Forbidden
{
"type": "about:blank",
"title": "Forbidden",
"status": 403,
"code": "insufficient_scope",
"detail": "This endpoint needs the routes:write scope."
}
Field Type Meaning
type string Always about:blank; the code identifies the problem instead.
title string Short human-readable summary.
status integer The HTTP status, repeated.
code string One of the codes below. Stable across releases.
detail string What went wrong with this particular request, for logs and error messages.
errors array Only on validation_error: one entry per invalid field, see below.
Code Status When
validation_error 400 The request body failed validation. errors lists each field and what was wrong with it.
unauthorized 401 The API key is missing, malformed, revoked or expired. Do not retry; fix the key.
insufficient_scope 403 The key is valid but was not created with the scope this endpoint needs. detail names the scope.
not_found 404 No route with that id belongs to this company. Ids are company-scoped, so a valid id from another company is also not_found.
route_active 409 The route has already started. Stops on an active route cannot change, and active or finished routes cannot be canceled through the API. Cancel is only for pending*, draft and planned routes; for an active one, push a new route.
rate_limited 429 More than 600 requests in a minute from this key. Wait the number of seconds in the Retry-After header, then retry. See Rate limits.
internal_error 500 Something failed on Novil’s side. Nothing about the request needs to change; retry it after a short delay.

A 400 carries an errors array naming each field that failed, using JSON paths into the request body:

400 Bad Request
{
"type": "about:blank",
"title": "Validation failed",
"status": 400,
"code": "validation_error",
"detail": "The request body is invalid.",
"errors": [
{ "field": "stops", "message": "At least two stops are required." },
{ "field": "stops[1].latitude", "message": "Latitude is required." }
]
}

The rules that most often trip a first integration, all from POST /v1/routes:

  • externalId, vehicle, driver, scheduledStart and stops are required.
  • stops needs at least 2 and at most 50 entries, each with name, latitude and longitude. Addresses are display-only; nothing is geocoded.
  • latitude is between -90 and 90, longitude between -180 and 180.
  • vehicle needs unitNumber or a 17-character vin; driver needs name or externalId.
  • scheduledStart and arrival windows are RFC 3339 timestamps with an offset.
  • externalId, reference and stop names are at most 200 characters; addresses at most 400.
Endpoint Possible errors
GET /v1/me 401
POST /v1/routes 400, 401, 403, 409 route_active, 429
GET /v1/routes 401, 403, 429
GET /v1/routes/{id} 401, 403, 404 not_found, 429
DELETE /v1/routes/{id} 401, 403, 404 not_found, 409, 429
GET /v1/vehicles 401, 403, 429
GET /v1/drivers 401, 403, 429
  • Treat 4xx other than 429 as bugs in the request or the configuration: log code and detail, do not retry blindly.
  • Retry 429 after Retry-After and 500 with a short backoff.
  • A 202 is not an error. It means the route was accepted and is being held (pending_mapping or pending_window); Novil retries on its own. See Matching vehicles and drivers.