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/cancellation-reasons |
any | Cancellation-reason catalogue for the caller's role |
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_*,
promo_credit_*, client_charged_*), solidary_fund_*, and delivery_pin. The
platform commission (aguita_revenue_*, and the aguita_revenue breakdown line)
is visible. When the order is paid in cash (CASH_USD), client_pays_*,
promo_credit_* and client_charged_* are also shown so the driver knows how
much to collect.client_charged_* is what the client actually pays: client_pays_* - promo_credit_*. With no promotional credit applied, promo_credit_* is 0 and
client_charged_* equals client_pays_*.
is_fully_promoted is a boolean flag indicating whether the order was 100% covered
by promotional credit (promo_credit_* >= client_pays_* and client_charged_* is 0).
The credit is applied automatically when the order is created, so POST /orders
already returns it discounted. The total can still move afterwards (an accepted
counter-offer, a payment-method change), and the credit is re-applied over the new
one. promo_credit_* never exceeds client_pays_*, so client_charged_* is never
negative. When a credit is applied the order also
carries a promo_credit breakdown line of type adjustment with a negative
amount_cents, hidden from the DRIVER view on non-cash orders.
Cash orders (CASH_USD) charge no solidary at all: client_solidary_*,
driver_solidary_* and solidary_fund_* are 0, client_pays_* equals
base_price_*, driver_receives_* equals driver_gross_*, and all three solidary
breakdown lines are 0 (applied_fee_prc 0). Online methods apply the full
1% + 1% split.
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",
"address_reference": "Frente a la panadería, portón azul",
"liters_requested": 5000,
"water_type": "POTABLE",
"payment_method": "CASH_USD",
"base_price_usd": 20.0,
"client_solidary_usd": 0.0,
"client_pays_usd": 20.0,
"promo_credit_usd": 0.0,
"client_charged_usd": 20.0,
"is_fully_promoted": false,
"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 |
addressReference |
string | null | no | max 255 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 |
billingProfileId |
integer | null | conditional | must be one of the caller's billing profiles. When omitted, the caller's default profile is used. Required when billing_required is on and the caller has no profile |
Response 201 — { "order": { … } } (CLIENT view). Cash orders start at
CONFIRMED; online methods start at WAITING_FOR_PAYMENT.
The order carries a billing object with the billing data as it stood when the
order was created, or null when there was none:
{
"billing": {
"entity_type": "JURIDICA",
"id_number": "J-123456789",
"name": "Distribuidora Aguas del Valle, C.A.",
"address": "Av. Libertador, Torre Norte, Piso 3, Caracas",
"phone": "+58 212 5551234"
}
}
billing.address is the billing address and is unrelated to the order's
address, which is where the water is delivered. billing is present in the
CLIENT and ADMIN views of an order; the DRIVER view never includes it.
Errors
| Status | Body | When |
|---|---|---|
409 |
{ "error": "ActiveOrderExists", "message": "…" } |
The customer already has an active order |
422 |
{ "error": "ExchangeRateUnavailable", "message": "…" } |
There is no active BCV (VES) rate to snapshot on the order |
422 |
{ "error": "ValidationError", "details": { "fieldErrors": { "billingProfileId": [ … ] } } } |
billingProfileId is unknown or belongs to another customer, or billing is required and the customer has no billing profile |
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.0,
"promo_credit_usd": 0.0,
"client_charged_usd": 20.0,
"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, or zero-cost order not marked as valid promotion |
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, promo_credit_usd and client_charged_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_code |
string | no | a CancellationReason code valid for the caller's role (client or driver) |
reason |
string | no | free-text note; ≤ 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 |
422 |
{ "error": "ValidationError", … } |
reason_code not in the caller's role catalogue |
Cancellation-reason catalogue for the authenticated caller's active role (client or driver). Feeds the cancel dropdown.
Response 200
{ "reasons": [ { "code": "CLIENT_CHANGED_MIND", "label": "Cambié de opinión" } ] }
Reason codes by role:
CLIENT_CHANGED_MIND, CLIENT_PRICE_TOO_HIGH, CLIENT_DELAY,
CLIENT_DUPLICATE, CLIENT_FOUND_ALTERNATIVE, CLIENT_OTHER.DRIVER_TOO_FAR, DRIVER_NO_CAPACITY, DRIVER_CLIENT_UNREACHABLE,
DRIVER_VEHICLE_ISSUE, DRIVER_OTHER.admin-orders.md): ADMIN_FRAUD, ADMIN_TEST,
ADMIN_SUPPORT, ADMIN_DUPLICATE, ADMIN_OTHER.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",
"is_fully_promoted": false,
"breakdown": {
"base_price_usd": 20.0,
"client_solidary_usd": 0.0,
"client_pays_usd": 20.0,
"promo_credit_usd": 0.0,
"client_charged_usd": 20.0,
"is_fully_promoted": false,
"aguita_commission_usd": 3.6,
"driver_gross_usd": 16.4,
"driver_solidary_usd": 0.0,
"driver_receives_usd": 16.4,
"solidary_fund_usd": 0.0
},
"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"}'