Admin drivers

Review and manage drivers (cisterneros): KYC, documents, status, certification, and their wallets. Paths are relative to the /api/v1 prefix; these are back-office endpoints requiring an admin token.

Requires an admin bearer token (see Admin auth). Admin only.

See Common errors.

For the list and stats, an admin only sees drivers with activity in their assigned cities. Wallet money fields are USD (*_usd).

Endpoints

Method URI Access Description
GET /admin/drivers/stats Admin Counters: active / pending review / suspended
GET /admin/drivers Admin Paginated driver list with filters
GET /admin/drivers/{driverId} Admin Full driver detail (driver, vehicle, documents, wallet)
GET /admin/drivers/{driverId}/documents Admin KYC documents with validity state
PATCH /admin/drivers/{driverId}/documents/{documentId}/approve Admin Approve a single document
PATCH /admin/drivers/{driverId}/documents/{documentId}/reject Admin Reject a single document
PATCH /admin/drivers/{driverId}/certify Admin Mark the driver as certified
PATCH /admin/drivers/{driverId}/status Admin Change driver status (activate/suspend/ban)
POST /admin/drivers/{driverId}/kyc/approve Admin Approve KYC
POST /admin/drivers/{driverId}/kyc/reject Admin Reject KYC
POST /admin/drivers/{driverId}/wallet/settle Admin Settle the wallet balance into a payout
POST /admin/drivers/{driverId}/wallet/adjust Admin Manually credit/debit the wallet
GET /admin/drivers/{driverId}/transactions Admin Paginated wallet transactions
GET /admin/drivers/{driverId}/transactions/export Admin Export wallet transactions as CSV

The driver detail object

show, certify, kyc/approve, and kyc/reject all return the same full payload:

{
  "driver": {
    "id": 3,
    "user": {
      "id": 7,
      "name": "Carlos Rodríguez",
      "phone": "+584241001001",
      "email": "carlos@example.com",
      "created_at": "2026-05-01T09:00:00+00:00"
    },
    "status": "ACTIVE",
    "kyc_state": "APPROVED",
    "kyc_review_note": null,
    "kyc_submitted_at": "2026-05-02T10:00:00+00:00",
    "kyc_reviewed_at": "2026-05-03T11:00:00+00:00",
    "is_online": true,
    "avg_rating": 4.8,
    "total_ratings": 25,
    "completed_orders": 120,
    "total_liters_delivered": 600000,
    "available_liters": 5000,
    "current_lat": 10.49,
    "current_lng": -66.85,
    "last_location_at": "2026-06-29T12:34:00+00:00",
    "is_certified": true,
    "created_at": "2026-05-01T09:00:00+00:00"
  },
  "vehicle": { "id": 4, "plate": "ABC-123", "capacity_liters": 5000, "status": "ACTIVE" },
  "documents": [
    {
      "id": 1,
      "type": "LICENSE",
      "url": "https://…",
      "status": "APPROVED",
      "review_note": null,
      "reviewed_at": "2026-05-03T11:00:00+00:00",
      "expires_at": "2027-01-01T00:00:00+00:00",
      "is_expired": false
    }
  ],
  "wallet": {
    "balance_usd": 124.5,
    "pending_payout_usd": 0,
    "cash_debt_usd": 0,
    "cash_debt_cap_usd": 50
  }
}

vehicle and wallet are null when the driver has none.


GET /admin/drivers/stats

Counters for the dashboard cards, scoped to the admin's cities.

Response 200

{ "total_active": 12, "total_pending_review": 3, "total_suspended": 1 }

GET /admin/drivers

Page-paginated driver list (newest first). See Pagination → page-based.

Query

Field Type Required Rules
kyc_state string no PENDING_KYC, PENDING_REVIEW, APPROVED, REJECTED
status string no PENDING_VERIFICATION, ACTIVE, SUSPENDED, BANNED
is_online boolean no
min_rating number no between 0 and 5
q string no ≤ 80 chars; matches name, phone, or vehicle plate
limit integer no 1–100, default 25
page integer no ≥ 1, default 1

Response 200

{
  "items": [
    {
      "id": 3,
      "name": "Carlos Rodríguez",
      "phone": "+584241001001",
      "status": "ACTIVE",
      "kyc_state": "APPROVED",
      "is_online": true,
      "avg_rating": 4.8,
      "completed_orders": 120,
      "available_liters": 5000,
      "balance_usd": 124.5
    }
  ],
  "meta": { "total": 42, "per_page": 25, "current_page": 1, "last_page": 2 }
}

GET /admin/drivers/{driverId}

Full driver detail. See the driver detail object.

Response 200{ "driver": { … }, "vehicle": { … }, "documents": [ … ], "wallet": { … } }.

Errors

Status Body When
404 { "error": "DriverNotFound" } No driver with that id

GET /admin/drivers/{driverId}/documents

Lists the driver's KYC documents with their expiry state.

Response 200

{
  "driver_id": 3,
  "items": [
    {
      "id": 1,
      "type": "LICENSE",
      "url": "https://…",
      "approval_status": "APPROVED",
      "review_note": null,
      "reviewed_at": "2026-05-03T11:00:00+00:00",
      "expires_at": "2027-01-01T00:00:00+00:00",
      "validity_state": "vigente",
      "days_to_expire": 186
    }
  ]
}

validity_state is one of vigente, por_vencer (expires within 30 days), vencido (past expires_at), or sin_vencimiento (no expiry). days_to_expire is null when the document has no expires_at.

Errors

Status Body When
404 { "error": "DriverNotFound" } No driver with that id

PATCH /admin/drivers/{driverId}/documents/{documentId}/approve

Marks a single document as approved.

Response 200

{
  "document": {
    "id": 1,
    "type": "LICENSE",
    "url": "https://…",
    "status": "APPROVED",
    "review_note": null,
    "reviewed_at": "2026-06-29T12:00:00+00:00",
    "expires_at": "2027-01-01T00:00:00+00:00"
  }
}

Errors

Status Body When
404 { "error": "DriverNotFound" } No driver with that id
404 { "error": "DocumentNotFound" } Document not found for this driver

PATCH /admin/drivers/{driverId}/documents/{documentId}/reject

Rejects a single document with a reason.

Request body

Field Type Required Rules
reason string yes 10–500 chars

Response 200 — same { "document": { … } } shape with status: "REJECTED" and review_note set.

Errors

Status Body When
404 { "error": "DriverNotFound" } No driver with that id
404 { "error": "DocumentNotFound" } Document not found for this driver

PATCH /admin/drivers/{driverId}/certify

Marks the driver as certified. Returns the full driver detail object.

Response 200{ "driver": { … }, "vehicle": { … }, "documents": [ … ], "wallet": { … } }.

Errors

Status Body When
404 { "error": "DriverNotFound" } No driver with that id

PATCH /admin/drivers/{driverId}/status

Changes the driver's lifecycle status.

Request body

Field Type Required Rules
status string yes ACTIVE, SUSPENDED, or BANNED
reason string conditional ≤ 500 chars; required when suspending or banning

Response 200

{ "driver": { "id": 3, "status": "SUSPENDED" } }

Errors

Status Body When
422 { "error": "ReasonRequired", "message": "…" } Suspending/banning without a reason
404 { "error": "DriverNotFound" } No driver with that id
422 { "error": "AlreadyInStatus", "message": "…" } Driver is already in the requested status
422 { "error": "NoVehicle", "message": "…" } Activating a driver that has no vehicle

POST /admin/drivers/{driverId}/kyc/approve

Approves a KYC submission. The driver must be in PENDING_REVIEW. Returns the full driver detail object.

Request body

Field Type Required Rules
note string no ≤ 500 chars

Response 200{ "driver": { … }, "vehicle": { … }, "documents": [ … ], "wallet": { … } } with kyc_state: "APPROVED".

Errors

Status Body When
404 { "error": "DriverNotFound" } No driver with that id
422 { "error": "InvalidKycState", "message": "…" } Driver is not in PENDING_REVIEW

POST /admin/drivers/{driverId}/kyc/reject

Rejects a KYC submission with a required reason. The driver must be in PENDING_REVIEW. Returns the full driver detail object.

Request body

Field Type Required Rules
reason string yes 10–500 chars

Response 200{ "driver": { … }, "vehicle": { … }, "documents": [ … ], "wallet": { … } } with kyc_state: "REJECTED".

Errors

Status Body When
404 { "error": "DriverNotFound" } No driver with that id
422 { "error": "InvalidKycState", "message": "…" } Driver is not in PENDING_REVIEW

POST /admin/drivers/{driverId}/wallet/settle

Pays out the entire current wallet balance as a PENDING payout.

Response 200

{
  "payout": {
    "id": 10,
    "amount_usd": 124.5,
    "method": "PAGO_MOVIL",
    "status": "PENDING",
    "scheduled_at": null
  }
}

Errors

Status Body When
404 { "error": "DriverNotFound" } No driver with that id
422 { "error": "NoWallet", "message": "…" } The driver has no wallet
422 { "error": "NoPayoutMethod", "message": "…" } No payout method configured
422 { "error": "InsufficientBalance", "message": "…" } Balance is zero or negative

POST /admin/drivers/{driverId}/wallet/adjust

Applies a manual credit or debit. A debit cannot push the balance below zero.

Request body

Field Type Required Rules
type string yes credit or debit
amount number yes ≥ 0.01 (USD)
reason string yes 10–500 chars

Response 200

{
  "transaction": {
    "id": 55,
    "type": "MANUAL_ADJUSTMENT",
    "amount_usd": 10.0,
    "balance_after_usd": 134.5,
    "description": "credit manual: ajuste por reclamo",
    "created_at": "2026-06-29T12:00:00+00:00"
  }
}

Errors

Status Body When
404 { "error": "DriverNotFound" } No driver with that id

GET /admin/drivers/{driverId}/transactions

Page-paginated wallet transactions (newest first). See Pagination → page-based. A driver with no wallet returns an empty list with 200.

Query

Field Type Required Rules
type string no one of: ORDER_EARNING, PAYOUT_SENT, MANUAL_ADJUSTMENT
from date no filter created_at >=
to date no filter created_at <=
limit integer no 1–100, default 25
page integer no ≥ 1, default 1

Response 200

{
  "items": [
    {
      "id": 55,
      "type": "ORDER_EARNING",
      "is_credit": true,
      "amount_usd": 16.24,
      "balance_after_usd": 124.5,
      "exchange_rate_amount": 540.043,
      "description": "Ganancia pedido #42",
      "order_id": 42,
      "payout_id": null,
      "created_at": "2026-06-29T12:00:00+00:00"
    }
  ],
  "meta": { "total": 80, "per_page": 25, "current_page": 1, "last_page": 4 }
}

exchange_rate_amount is the BCV VES rate (Bs/USD) snapshot of the related order; null for movements without an order (PAYOUT_SENT, MANUAL_ADJUSTMENT).

Errors

Status Body When
404 { "error": "DriverNotFound" } No driver with that id

GET /admin/drivers/{driverId}/transactions/export

Streams the filtered wallet transactions as a CSV download (Content-Type: text/csv); there is no JSON body. Accepts the same type, from, and to filters as the list. Columns: id, fecha, tipo, credito, monto_usd, saldo_despues_usd, tasa_cambio, descripcion, order_id, payout_id.

Errors

Status Body When
404 { "error": "DriverNotFound" } No driver with that id