The customer-facing order lifecycle: create an order, track the driver, confirm
delivery, and read receipts. Paths are relative to the /api/v1 prefix; all routes
require Authorization: Bearer {token}.
Driver bidding (accept / counter-offer) and the client accept/reject of offers live under Order offers (
/orders/{id}/offer,/orders/{id}/accept,/counter-offers/{id}/accept). Driver-side listings live under Drivers (/driver/orders,/driver/online). Both are documented separately.
See Common errors and the order lifecycle.
| Method | URI | Role | Description |
|---|---|---|---|
POST |
/orders |
CLIENT | Create an order (starts broadcasting) |
GET |
/orders |
CLIENT | Paginated order history (cursor) |
GET |
/orders/active |
CLIENT | The caller's current active order |
GET |
/orders/history |
CLIENT | Recent orders (flat list) |
POST |
/orders/{orderId}/start |
DRIVER | Start the assigned trip |
POST |
/orders/{orderId}/location |
DRIVER | Push the driver's live location |
POST |
/orders/{orderId}/arrived |
DRIVER | Mark arrival at the destination |
GET |
/orders/{orderId}/tracking |
CLIENT | Live driver position, distance & ETA |
POST |
/orders/{orderId}/deliver |
DRIVER | Confirm delivery with the PIN |
PATCH |
/orders/{orderId}/payment-method |
CLIENT | Change the order's payment method |
POST |
/orders/{orderId}/cancel |
any | Cancel the order |
GET |
/orders/{orderId}/receipt |
CLIENT | Accounting receipt (delivered orders) |
GET |
/orders/{orderId} |
any | Order detail (owner or assigned driver) |
Single-order responses wrap the order under an order key. The payload is the order
record filtered by role:
aguita_revenue_*,
solidary_fund_*, driver_solidary_*, driver_gross_*, driver_receives_*).client_solidary_*, client_pays_*),
platform fields, and delivery_pin.delivery_pin is additionally only present once the order is paid
(CONFIRMED/IN_PROGRESS/ARRIVED).
Representative CLIENT view:
{
"order": {
"id": 42,
"status": "CONFIRMED",
"latitude": 10.487,
"longitude": -66.88,
"address": "Av. Principal, Las Mercedes",
"liters_requested": 5000,
"water_type": "POTABLE",
"payment_method": "CASH_USD",
"base_price_usd": 20.0,
"client_solidary_usd": 0.2,
"client_pays_usd": 20.2,
"delivery_pin": "1234",
"notes": null,
"created_at": "2026-06-26T12:00:00+00:00"
}
}
Creates an order and starts broadcasting to nearby online drivers. A customer may only have one active order at a time.
Request body
| Field | Type | Required | Rules |
|---|---|---|---|
latitude |
number | yes | between −90 and 90 |
longitude |
number | yes | between −180 and 180 |
address |
string | yes | 3–280 chars |
litersRequested |
integer | yes | 1–50000 |
basePrice |
number | yes | > 0, ≤ 100000 |
waterType |
string | no | POTABLE or INDUSTRIAL |
paymentMethod |
string | no | an active method code or PAYMENT_FORM; defaults to CASH_USD |
paymentFormId |
integer | conditional | required when paymentMethod=PAYMENT_FORM; must be an active payment form |
notes |
string | no | ≤ 500 chars |
accessInstructions |
string | no | ≤ 500 chars |
Response 201 — { "order": { … } } (CLIENT view). Cash orders start at
CONFIRMED; online methods start at WAITING_FOR_PAYMENT.
Errors
| Status | Body | When |
|---|---|---|
409 |
{ "error": "ActiveOrderExists", "message": "…" } |
The customer already has an active order |
Cursor-paginated history for the authenticated customer. See Pagination → cursor-based.
Query: status, date_from, date_to, limit (1–100, default 25), cursor.
Response 200
{
"items": [
{
"id": 42,
"status": "DELIVERED",
"liters_requested": 5000,
"water_type": "POTABLE",
"address": "Av. Principal, Las Mercedes",
"client_pays_usd": 20.2,
"payment_method": "CASH_USD",
"driver": { "id": 3, "name": "Carlos Rodríguez" },
"expires_at": "2026-06-26T12:05:00+00:00",
"created_at": "2026-06-26T12:00:00+00:00",
"delivered_at": "2026-06-26T12:40:00+00:00"
}
],
"next_cursor": "eyJpZCI6NDF9",
"has_more": true
}
Returns the caller's current active order, or null.
Response 200 — { "order": { … } } or { "order": null }. The
delivery_pin is stripped unless the order is paid.
Recent orders as a flat list.
Query: limit (1–100, default 20).
Response 200 — { "orders": [ { … } ] } (each entry is a CLIENT-view order).
Driver starts the assigned trip (CONFIRMED → IN_PROGRESS).
Response 200 — { "order": { … } } (DRIVER view).
| Status | Body | When |
|---|---|---|
409 |
{ "error": "Conflict", "message": "…" } |
Order not in a startable state |
Push the driver's live GPS while serving an order (IN_PROGRESS/ARRIVED).
Request body
| Field | Type | Required | Rules |
|---|---|---|---|
latitude |
number | yes | between −90 and 90 |
longitude |
number | yes | between −180 and 180 |
Response 204 No Content.
| Status | Body | When |
|---|---|---|
404 |
{ "error": "OrderNotFound" } |
Unknown order |
403 |
{ "error": "Forbidden" } |
Caller is not the assigned driver |
409 |
{ "error": "WrongStatus", "message": "…" } |
Order not IN_PROGRESS/ARRIVED |
Marks arrival. The driver must be within 500 m of the destination; passing
latitude/longitude updates the live location first. Idempotent once ARRIVED.
Request body
| Field | Type | Required | Rules |
|---|---|---|---|
latitude |
number | no | between −90 and 90 |
longitude |
number | no | between −180 and 180 |
Response 200
{ "ok": true, "status": "ARRIVED", "arrivedAt": "2026-06-26T12:35:00+00:00", "distanceMeters": 120.4 }
Errors
| Status | Body | When |
|---|---|---|
404 |
{ "error": "OrderNotFound" } |
Unknown order |
403 |
{ "error": "Forbidden" } |
Caller is not the assigned driver |
409 |
{ "error": "WrongStatus", "message": "…" } |
Order not IN_PROGRESS |
409 |
{ "error": "NoLocation", "message": "…" } |
No recent driver location |
409 |
{ "error": "TooFarFromDestination", "message": "…", "distanceKm": 1.8 } |
Over 500 m away |
Live driver position, straight-line distance, and a rough ETA (25 km/h). Only the
order owner, only while CONFIRMED/IN_PROGRESS/ARRIVED.
Response 200
{
"order": { "id": 42, "status": "IN_PROGRESS", "latitude": 10.487, "longitude": -66.88, "address": "Av. Principal", "expires_at": "2026-06-26T12:05:00+00:00" },
"driver": {
"id": 3,
"name": "Carlos Rodríguez",
"phone": "+584241001001",
"currentLat": 10.49,
"currentLng": -66.85,
"lastLocationAt": "2026-06-26T12:34:00+00:00"
},
"distanceKm": 1.2,
"estimatedMinutes": 3,
"avgSpeedKmh": 25
}
driver is null (with distanceKm/estimatedMinutes null) until the driver has a
known location.
| Status | Body | When |
|---|---|---|
403 |
{ "error": "Forbidden" } |
Caller is not the order owner |
409 |
{ "error": "NoTracking", "message": "…" } |
Order not in a trackable state |
Driver confirms delivery using the customer's 4-digit PIN.
Request body
| Field | Type | Required | Rules |
|---|---|---|---|
actualLiters |
integer | yes | 1–50000 |
deliveryPin |
string | yes | exactly 4 digits |
Response 200 — { "ok": true }.
| Status | Body | When |
|---|---|---|
400 |
{ "error": "DeliveryError", "message": "…PIN…" } |
Wrong PIN |
409 |
{ "error": "DeliveryError", "message": "…" } |
Order not deliverable |
Changes the order's payment method. Owner only.
Request body
| Field | Type | Required | Rules |
|---|---|---|---|
paymentMethod |
string | yes | an active method code or PAYMENT_FORM |
paymentFormId |
integer | conditional | required when paymentMethod=PAYMENT_FORM; must be an active payment form; ignored otherwise |
Response 200 — { "order": { … } } (CLIENT view) with the updated
payment_method; client_pays_usd may change.
Errors
| Status | Body | When |
|---|---|---|
404 |
{ "error": "OrderNotFound", "message": "…" } |
Unknown order or not owned by the caller |
409 |
{ "error": "PaymentMethodLocked", "message": "…" } |
Order past WAITING_FOR_PAYMENT, the client already reported a payment, or switching to cash after acceptance |
422 |
{ "error": "…" } |
Method not available, or missing/invalid paymentFormId |
Cancels the order. Allowed for the order's customer or assigned driver (and admins).
Request body
| Field | Type | Required | Rules |
|---|---|---|---|
reason |
string | no | ≤ 280 chars |
Response 200 — { "order": { … } } (filtered for the caller's role).
| Status | Body | When |
|---|---|---|
403 |
{ "error": "CancelError", "message": "…" } |
Caller may not cancel this order |
404 |
{ "error": "CancelError", "message": "…" } |
Order not found |
409 |
{ "error": "CancelError", "message": "…" } |
Order not cancellable |
Full accounting receipt for a delivered order, owner only.
Response 200
{
"receipt": {
"order_id": 42,
"status": "DELIVERED",
"delivered_at": "2026-06-26T12:40:00+00:00",
"liters_requested": 5000,
"actual_liters": 5000,
"water_type": "POTABLE",
"payment_method": "CASH_USD",
"breakdown": {
"base_price_usd": 20.0,
"client_solidary_usd": 0.2,
"client_pays_usd": 20.2,
"aguita_commission_usd": 3.6,
"driver_gross_usd": 16.4,
"driver_solidary_usd": 0.16,
"driver_receives_usd": 16.24,
"solidary_fund_usd": 0.36
},
"driver": {
"id": 3,
"name": "Carlos Rodríguez",
"phone": "+584241001001",
"vehicle": { "plate": "ABC-123", "capacity_liters": 5000 }
},
"rating": { "score": 5, "comment": "Puntual", "tags": ["fast"], "created_at": "2026-06-26T12:45:00+00:00" }
}
}
rating is null until the customer rates the order. See
Business → money breakdown.
| Status | Body | When |
|---|---|---|
403 |
{ "error": "Forbidden" } |
Caller is not the order owner |
409 |
{ "error": "OrderNotDelivered", "message": "…" } |
Order not delivered yet |
Order detail for the owner or the assigned driver. The payload is role-filtered, and
delivery_pin is present only when the order is paid.
For the client, each pending order.offers[] adds distance_km (number|null),
eta_minutes (number|null), and driver_busy (boolean). offers[].driver exposes
only public fields — raw coordinates are not included. Same shape on
GET /orders/active.
Response 200 — { "order": { … } }.
| Status | Body | When |
|---|---|---|
404 |
{ "error": "OrderNotFound" } |
Unknown order |
403 |
{ "error": "Forbidden" } |
Caller is neither owner nor assigned driver |
# Create (cash) → broadcasting/confirmed
curl -X POST /api/v1/orders \
-H "Authorization: Bearer {client_token}" \
-H "Content-Type: application/json" \
-d '{"latitude": 10.487, "longitude": -66.88, "address": "Av. Principal", "litersRequested": 5000, "basePrice": 20}'
# Driver: live location, then mark arrived
curl -X POST /api/v1/orders/42/location \
-H "Authorization: Bearer {driver_token}" \
-H "Content-Type: application/json" \
-d '{"latitude": 10.4895, "longitude": -66.8805}'
# Driver: confirm delivery with the client's PIN
curl -X POST /api/v1/orders/42/deliver \
-H "Authorization: Bearer {driver_token}" \
-H "Content-Type: application/json" \
-d '{"actualLiters": 5000, "deliveryPin": "1234"}'