The driver onboarding flow: upload KYC documents, register the vehicle, submit for
review, and check the verification status. Paths are relative to the /api/v1
prefix; every route requires Authorization: Bearer {token} and the DRIVER role.
See Common errors and Fleet & KYC for how drivers, vehicles, and documents relate.
| Method | URI | Role | Description |
|---|---|---|---|
POST |
/driver/documents |
DRIVER | Upload (or replace) a KYC document |
GET |
/driver/vehicle |
DRIVER | Get the driver's vehicle |
PATCH |
/driver/vehicle |
DRIVER | Create or update the driver's vehicle |
POST |
/driver/kyc/submit |
DRIVER | Submit KYC for admin review |
GET |
/driver/kyc/status |
DRIVER | Current KYC state, documents, and gaps |
KYC states are PENDING_KYC, PENDING_REVIEW, APPROVED, and REJECTED. Only
ID_FRONT and LICENSE are required to submit; a vehicle is also required.
Uploads a KYC document as multipart/form-data. Re-uploading the same type replaces
it, and the document's status returns to PENDING.
Request body (multipart/form-data)
| Field | Type | Required | Rules |
|---|---|---|---|
type |
string | yes | one of ID_FRONT, ID_BACK, LICENSE, MEDICAL_CERT, RCV, ADDRESS_PROOF, VEHICLE_PHOTO, SANITARY_CERT |
file |
file | yes | ≤ 10 MB; jpg, jpeg, png, pdf, or webp |
expires_at |
date | conditional | must be after today; required for LICENSE, MEDICAL_CERT, RCV, SANITARY_CERT |
Response 201
{
"document": {
"id": 17,
"type": "LICENSE",
"url": "https://…/storage/kyc/3/LICENSE-20260626120000.jpg",
"status": "PENDING",
"expires_at": "2027-06-26T00:00:00+00:00"
}
}
Errors
| Status | Body | When |
|---|---|---|
404 |
{ "error": "DriverNotFound" } |
The caller has no driver profile |
422 |
{ "error": "ExpiresAtRequired", "message": "…" } |
The document type requires expires_at and none was given |
Returns the driver's single vehicle.
Response 200
{
"vehicle": {
"id": 4,
"driver_id": 3,
"plate": "ABC-123",
"capacity_liters": 5000,
"brand": "Ford",
"model": "Cargo",
"year": 2018,
"hose_length_meters": 30,
"has_motopump": true,
"operation_radius_km": 15,
"photo_urls": [],
"sanitary_cert_url": null,
"cert_expires_at": null,
"status": "PENDING",
"created_at": "2026-06-20T10:00:00.000000Z",
"updated_at": "2026-06-20T10:00:00.000000Z"
}
}
Errors
| Status | Body | When |
|---|---|---|
404 |
{ "error": "DriverNotFound" } |
The caller has no driver profile |
404 |
{ "error": "VehicleNotFound" } |
The driver has no vehicle registered |
Creates or updates the driver's single vehicle. The plate must be unique across drivers.
Request body
| Field | Type | Required | Rules |
|---|---|---|---|
plate |
string | yes | ≤ 20 chars |
capacity_liters |
integer | yes | 500–50000 |
brand |
string | no | ≤ 60 chars |
model |
string | no | ≤ 60 chars |
year |
integer | no | 1980 – next year |
hose_length_meters |
integer | no | 1–100 |
has_motopump |
boolean | no | — |
operation_radius_km |
integer | no | 1–100 |
sanitary_cert_url |
string | no | valid URL, ≤ 500 chars |
cert_expires_at |
date | no | after today |
Response 200
{
"vehicle": {
"id": 4,
"driver_id": 3,
"plate": "ABC-123",
"capacity_liters": 5000,
"brand": "Ford",
"model": "Cargo",
"year": 2018,
"hose_length_meters": 30,
"has_motopump": true,
"operation_radius_km": 15,
"status": "PENDING",
"created_at": "2026-06-20T10:00:00+00:00"
}
}
Errors
| Status | Body | When |
|---|---|---|
404 |
{ "error": "DriverNotFound" } |
The caller has no driver profile |
409 |
{ "error": "PlateAlreadyUsed", "message": "…" } |
The plate is registered to another driver |
Submits KYC for admin review. Requires the vehicle and all required documents to be present, and KYC not to be already submitted or approved.
Response 200
{ "kyc": { "state": "PENDING_REVIEW", "submittedAt": "2026-06-26T13:00:00+00:00" } }
Errors
| Status | Body | When |
|---|---|---|
404 |
{ "error": "DriverNotFound" } |
The caller has no driver profile |
409 |
{ "error": "WrongKycState", "message": "…", "current": "PENDING_REVIEW" } |
KYC is already submitted or approved |
422 |
{ "error": "VehicleMissing", "message": "…" } |
No vehicle registered |
422 |
{ "error": "DocumentsMissing", "message": "…", "missing": ["LICENSE"] } |
Required documents are missing |
The current KYC state, the uploaded documents, and what is still missing to submit.
Response 200
{
"state": "PENDING_KYC",
"reviewNote": null,
"submittedAt": null,
"reviewedAt": null,
"documents": [
{
"type": "ID_FRONT",
"status": "PENDING",
"expires_at": null,
"reviewNote": null
}
],
"requiredMissing": ["LICENSE"],
"hasVehicle": false
}
reviewNote carries the admin's note when the state is REJECTED. requiredMissing
lists the required document types not yet uploaded.
Errors
| Status | Body | When |
|---|---|---|
404 |
{ "error": "DriverNotFound" } |
The caller has no driver profile |
# Upload the driver's license (requires an expiry date)
curl -X POST /api/v1/driver/documents \
-H "Authorization: Bearer {driver_token}" \
-F "type=LICENSE" \
-F "expires_at=2027-06-26" \
-F "file=@license.jpg"
# Register the vehicle
curl -X PATCH /api/v1/driver/vehicle \
-H "Authorization: Bearer {driver_token}" \
-H "Content-Type: application/json" \
-d '{"plate": "ABC-123", "capacity_liters": 5000}'
# Read the vehicle
curl /api/v1/driver/vehicle -H "Authorization: Bearer {driver_token}"
# Submit for review, then poll status
curl -X POST /api/v1/driver/kyc/submit -H "Authorization: Bearer {driver_token}"
curl /api/v1/driver/kyc/status -H "Authorization: Bearer {driver_token}"