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/adjust Admin Manually credit/debit the wallet
POST /admin/drivers/{driverId}/wallet/settle-cash-debt Admin Settle the driver's cash debt
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
  },
  "payout_config": {
    "method": "PAGO_MOVIL",
    "bank_code": "0102",
    "id_number": "12345678",
    "phone": "+584241234567",
    "account_number": null,
    "account_type": null,
    "updated_at": "2026-06-01T10:00:00+00:00"
  }
}

vehicle, wallet, and payout_config are null when the driver has none.

payout_config.method is PAGO_MOVIL or BANK_TRANSFER. For PAGO_MOVIL, bank_code + id_number + phone are set and account_number + account_type are null. For BANK_TRANSFER, account_number + account_type are set.


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/adjust

Applies a manual credit or debit. A debit may take the balance negative, so balance_after_usd can be negative.

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",
    "direction": "CREDIT",
    "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

POST /admin/drivers/{driverId}/wallet/settle-cash-debt

Settles the driver's outstanding cash debt (cashDebtUSD). mode picks the accounting: external_payment lowers only the cash debt (balance_after_usd unchanged); offset_earnings lowers the cash debt and the balance by the same amount, and requires the driver's withdrawable earnings to cover it. When amount is omitted the full outstanding debt is settled.

Request body

Field Type Required Rules
mode string yes external_payment or offset_earnings
amount number no ≥ 0.01 (USD); ≤ current cash debt; omitted settles the full debt
reason string yes 10–500 chars

Response 200

{
  "transaction": {
    "id": 71,
    "type": "CASH_DEBT_PAYMENT",
    "direction": "CREDIT",
    "amount_usd": 3.6,
    "balance_after_usd": 124.5,
    "description": "Saldo de deuda en efectivo (pago externo): pagó en oficina",
    "created_at": "2026-06-29T12:00:00+00:00"
  },
  "wallet": {
    "balance_usd": 124.5,
    "cash_debt_usd": 0.0,
    "can_go_online": true
  }
}

transaction.type is CASH_DEBT_PAYMENT for external_payment (direction CREDIT) or CASH_DEBT_OFFSET for offset_earnings (direction DEBIT).

Errors

Status Body When
404 { "error": "DriverNotFound" } No driver with that id
409 { "error": "SettlementInProgress", "message": "…" } Another settle request for this driver is being processed concurrently
409 { "error": "PayoutInProgress", "message": "…" } A generated-but-unpaid settlement receipt already accounts for this debt
422 { "error": "NoCashDebt", "message": "…" } The driver has no outstanding cash debt
422 { "error": "AmountExceedsCashDebt", "message": "…" } amount is greater than the current cash debt
422 { "error": "InsufficientEarnings", "message": "…" } mode is offset_earnings but the withdrawable earnings do not cover the amount

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, PROMO_BONUS, PROMO_REIMBURSEMENT
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",
      "direction": "CREDIT",
      "is_credit": true,
      "amount_usd": 16.24,
      "balance_after_usd": 124.5,
      "exchange_rate_amount": 540.043,
      "description": "Ganancia pedido #42",
      "order_id": 42,
      "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, PROMO_BONUS). PROMO_BONUS is a milestone campaign reward and PROMO_REIMBURSEMENT is the cash a client's promotional credit took off a cash order; both are CREDIT. direction is CREDIT (adds to the balance) or DEBIT (subtracts); is_credit is true exactly when direction is CREDIT.

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.

Errors

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