API reference
This page is for the developers of the systems that connect to the fleet (HR, planning, reporting). It summarises the REST API: how to authenticate, what each endpoint needs, and what it answers. The full reference, with every field and filter, is the OpenAPI 3.1 file: openapi.yaml.
Base URL and authentication
Section titled “Base URL and authentication”Every endpoint starts with your installation’s address followed by /api/v1, for example https://fleet.example.com/api/v1. The exact address is shown under Base URL on Settings → Integrations.
Send the token created on that screen with every request:
Authorization: Bearer <token>Accept: application/jsonThe browser session of a signed-in user is never accepted. A missing, wrong, revoked or expired token gets 401. The API always answers JSON, with messages in English.
Abilities, permissions and data scope
Section titled “Abilities, permissions and data scope”A request must pass three checks:
- The token’s abilities.
readallows everyGETendpoint;write:driversallows the driver sync (PUT drivers/by-iqama/{iqama}andPATCH drivers/{id}/status). A missing ability gets 403. - The token user’s permissions. The token acts as its user, so that user needs the permission the screens would need (see the table below). Otherwise the answer is 403, for example
The token user lacks the "Vehicles::viewAny" permission. - The token user’s projects and cities. A user limited to some projects or cities sees only those vehicles, their records, and the drivers they supervise or who hold one of those vehicles, exactly as on the screens. A record outside that scope answers 404.
Personal data follows the screens too: iqama_number is null unless the token user has the Drivers::view permission. Stored files are never exposed; records only say whether they have one (has_file, has_photo, photos_count).
Rate limit
Section titled “Rate limit”Each token may make 60 requests a minute. Responses carry X-RateLimit-Limit and X-RateLimit-Remaining; beyond the limit the answer is 429 with a Retry-After header (seconds).
Endpoints
Section titled “Endpoints”| Method | Path | Ability | Permission of the token user |
|---|---|---|---|
| GET | /vehicles |
read |
Vehicles::viewAny |
| GET | /vehicles/{id} |
read |
Vehicles::view |
| GET | /drivers |
read |
Drivers::viewAny |
| GET | /drivers/{id} |
read |
Drivers::view |
| GET | /contracts |
read |
Contracts::viewAny |
| GET | /contracts/{id} |
read |
Contracts::view |
| GET | /handovers |
read |
VehicleTransfers::viewAny |
| GET | /downtimes |
read |
VehicleDowntimes::viewAny |
| GET | /maintenances |
read |
VehicleMaintenances::viewAny |
| GET | /fuel-logs |
read |
VehicleFuelLogs::viewAny |
| GET | /accidents |
read |
Accidents::viewAny |
| GET | /violations |
read |
Violations::viewAny |
| GET | /invoices |
read |
Reports::MonthlyInvoices |
| GET | /projects |
read |
Projects::viewAny |
| GET | /cities |
read |
Cities::viewAny |
| GET | /companies |
read |
Companies::viewAny |
| PUT | /drivers/by-iqama/{iqama} |
write:drivers |
Drivers::create (new driver) or Drivers::edit (existing) |
| PATCH | /drivers/{id}/status |
write:drivers |
Drivers::edit |
In the role editor, viewAny is the List box of a row and view the View box; Reports::MonthlyInvoices is Monthly invoices under Reports. /invoices lists issued monthly invoices only, with the figures stored when they were issued.
List parameters
Section titled “List parameters”Every list takes:
| Parameter | Meaning |
|---|---|
page |
Page number, from 1. |
per_page |
10, 25, 50 or 100 (default 25). |
search |
Free text, up to 100 characters, matched against the list’s main fields (for example the plate number). |
sort |
id, created_at, updated_at, or a field of the list (for example license_plate, start_date). |
dir |
asc or desc. |
updated_since |
A date or date-time: only records changed at or after it, for incremental sync. |
And its own filters (ids are comma-separated, for example project=3,4):
| List | Filters |
|---|---|
/vehicles |
project, city, company, ownership_type (own, lease, replacement) |
/drivers |
status (active, inactive), supervisor, license_type |
/contracts |
vehicle, company, contract_type (own, lease), status (active, upcoming, expired) |
/handovers |
vehicle, driver, open (1: no end date yet, 0: ended) |
/downtimes |
vehicle, contract, status (inactive, replacement) |
/maintenances |
vehicle, driver, maintenance_type |
/fuel-logs, /accidents, /violations |
vehicle, driver |
/invoices |
month (YYYY-MM), contract, company |
A wrong parameter (for example per_page=20) gets 422.
Response format
Section titled “Response format”A list answers {data, meta, links}; a single record answers {data}. Each record is {id, type, attributes, relationships}: relationships gives the ids of related records (for example the vehicle’s project), which you can read from /projects, /cities and /companies. Dates are YYYY-MM-DD, timestamps ISO 8601, money a JSON number, and translated names {"ar": …, "en": …}.
curl -H "Authorization: Bearer $FLEET_TOKEN" \ -H "Accept: application/json" \ "https://fleet.example.com/api/v1/vehicles?per_page=10&project=3"{ "data": [ { "id": 12, "type": "vehicles", "attributes": { "license_plate": "ABC 1234", "brand": { "ar": "تويوتا", "en": "Toyota" }, "vehicle_type": { "ar": "سيدان", "en": "Sedan" }, "vehicle_sub_type": null, "fuel_type": { "ar": "بنزين 91", "en": "Petrol 91" }, "model_year": 2024, "ownership_type": "lease", "vehicle_license_number": "123456789", "vehicle_license_expiry_date": "2027-03-31", "has_waei": true, "owned_cost_bearer": null, "created_at": "2026-01-15T09:30:00+03:00", "updated_at": "2026-10-02T14:05:11+03:00" }, "relationships": { "project": { "id": 3, "type": "projects" }, "city": { "id": 1, "type": "cities" }, "company": { "id": 5, "type": "companies" } } } ], "meta": { "page": 1, "per_page": 10, "total": 1, "last_page": 1 }, "links": { "first": "https://fleet.example.com/api/v1/vehicles?per_page=10&project=3&page=1", "prev": null, "next": null, "last": "https://fleet.example.com/api/v1/vehicles?per_page=10&project=3&page=1" }}Driver sync
Section titled “Driver sync”PUT /drivers/by-iqama/{iqama}
Section titled “PUT /drivers/by-iqama/{iqama}”Creates the driver with this iqama number (201, needs Drivers::create) or updates the existing one (200, needs Drivers::edit), and answers the driver. Repeating the same request changes nothing. The rules are those of the driver form: the iqama number in the path is 10 digits starting with 1 or 2, and the licence number, iqama number and e-mail must be unique. A driver outside the token user’s projects and cities cannot be updated: the iqama number is reported as taken (422).
| Field | Rule |
|---|---|
name |
Required, up to 255 characters. |
license_number |
Required, up to 255 characters, unique. |
license_type_id |
Required, the id of a licence type. |
license_issued_date |
Required, a date. |
license_expiry_date |
Required, a date on or after license_issued_date. |
phone_number |
Required, up to 20 characters. |
email |
Optional, a valid e-mail, unique. |
supervisor_id |
Required, the id of a user. |
status |
Optional, active or inactive. A new driver is active; an existing one keeps their status. |
notes |
Optional, up to 1000 characters. |
The driver’s photo cannot be sent through the API.
PATCH /drivers/{id}/status
Section titled “PATCH /drivers/{id}/status”Activates or deactivates a driver. Body: {"status": "active"} or {"status": "inactive"}. Answers the driver (200).
Changes made through the API are recorded in the activity log, with the token’s user as the one who made them.
Errors
Section titled “Errors”Every error is JSON with a message:
| Status | When | Example |
|---|---|---|
| 401 | No token, or a wrong, revoked or expired one. | {"message": "Unauthenticated."} |
| 403 | The token lacks the ability, or its user lacks the permission. | {"message": "The token user lacks the \"Drivers::view\" permission."} |
| 404 | No such record or path, or the record is outside the user’s projects and cities. | {"message": "Not found."} |
| 422 | Invalid parameters or body; errors lists the messages per field. |
{"message": "The selected per page is invalid.", "errors": {"per_page": ["The selected per page is invalid."]}} |
| 429 | More than 60 requests in a minute with this token. | {"message": "Too Many Attempts."} |