End-of-route "surplus" listings: a driver publishes leftover water at a fixed price,
and a customer reserves the whole lot β which auto-creates an order. Paths are
relative to the /api/v1 prefix; all routes require Authorization: Bearer {token}.
See Common errors. A reservation creates an order, so Orders and the order lifecycle apply from there on.
| Method | URI | Role | Description |
|---|---|---|---|
POST |
/surplus |
DRIVER | Publish a surplus listing |
GET |
/surplus/me |
DRIVER | My active listings |
DELETE |
/surplus/{surplusId} |
DRIVER | Cancel a listing |
POST |
/surplus/{surplusId}/cancel |
DRIVER | Cancel a listing (alias of DELETE) |
GET |
/surplus/nearby |
any | Nearby active listings |
GET |
/surplus |
any | Nearby active listings (alias of /surplus/nearby) |
POST |
/surplus/{surplusId}/reserve |
CLIENT | Reserve a listing (creates an order) |
A driver may not publish a surplus listing while they have an active order. Money
fields end in _usd (decimal), derived from integer _cents.
Publishes a surplus listing. The driver enters totalAmount β the total gross they
want to receive for the whole listing. The listing expires after durationMinutes
(default 6 h if omitted).
Request body
| Field | Type | Required | Rules |
|---|---|---|---|
availableLiters |
integer | yes | 500β50000 |
totalAmount |
number | yes | > 0, β€ 100000 β total gross the driver wants to receive |
description |
string | no | β€ 280 chars |
latitude |
number | yes | between β90 and 90 |
longitude |
number | yes | between β180 and 180 |
address |
string | yes | 3β280 chars |
durationMinutes |
integer | no | 15β720 (max 12 h); defaults to 360 (6 h) |
Response 201
{
"listing": {
"id": 5,
"driver_id": 3,
"city_id": 1,
"available_liters": 5000,
"price_per_liter_cents": 2,
"total_price_cents": 10000,
"price_per_liter_usd": 0.02,
"total_price_usd": 100.0,
"description": "Excedente fin de ruta",
"latitude": 10.487,
"longitude": -66.88,
"address": "Av. Principal, Las Mercedes",
"status": "ACTIVA",
"sold_liters": 0,
"remaining_liters": 5000,
"expires_at": "2026-06-26T14:00:00+00:00",
"created_at": "2026-06-26T13:00:00+00:00"
}
}
Each listing carries a status: ACTIVA, RESERVADA (reserved β order created),
EXPIRADA (past expires_at), or CANCELADA.
Errors
| Status | Body | When |
|---|---|---|
409 |
{ "error": "Conflict", "message": "No podΓ©s publicar remates con una orden activa." } |
The driver already has an active order |
The caller's currently active listings (not expired, not cancelled, with liters left).
Response 200 β { "listings": [ { β¦ } ] } (each entry is a listing, same shape
as POST /surplus).
Cancels a listing (sets its status to CANCELADA). Only the listing's owner may cancel.
POST /surplus/{surplusId}/cancel is an exact alias.
Response 200 β { "ok": true }.
Errors
| Status | Body | When |
|---|---|---|
409 |
{ "error": "Conflict", "message": "No es tu listing." } |
The caller does not own the listing |
Alias of DELETE /surplus/{surplusId} β identical
behavior, response, and errors.
Active listings within a radius of a point, enriched with client-facing pricing
(base_price_* and client_pays_*). GET /surplus is an exact alias.
Pricing is previewed for paymentMethod (defaults to CASH_USD).
Query
| Field | Type | Required | Rules |
|---|---|---|---|
lat |
number | yes | between β90 and 90 |
lng |
number | yes | between β180 and 180 |
radiusKm |
number | no | > 0, β€ 100; defaults to 15 |
paymentMethod |
string | no | an active integration code; defaults to CASH_USD (PAYMENT_FORM is not supported here) |
Response 200
{
"listings": [
{
"id": 5,
"driver_id": 3,
"available_liters": 5000,
"remaining_liters": 5000,
"total_price_cents": 10000,
"total_price_usd": 100.0,
"price_per_liter_usd": 0.02,
"base_price_cents": 12195,
"base_price_usd": 121.95,
"client_pays_cents": 12317,
"client_pays_usd": 123.17,
"address": "Av. Principal, Las Mercedes",
"latitude": 10.487,
"longitude": -66.88,
"expires_at": "2026-06-26T14:00:00+00:00"
}
]
}
Alias of GET /surplus/nearby β identical query and response.
A customer reserves the whole listing. This creates an order (CONFIRMED for cash, WAITING_FOR_PAYMENT for online methods) and returns it in the CLIENT-sanitized shape (see Orders β the order object).
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 |
paymentMethod |
string | no | an active integration code; defaults to CASH_USD (PAYMENT_FORM is not supported here) |
Response 200 β { "order": { β¦ } } (CLIENT view). The order includes
surplus_listing_id (the listing it came from); a non-null value marks the order as a
surplus reservation. Normal orders have surplus_listing_id: null.
Errors
| Status | Body | When |
|---|---|---|
409 |
{ "error": "Conflict", "message": "El remate ya no estΓ‘ disponible." } |
The listing was sold, cancelled, or expired |
# Driver: publish surplus
curl -X POST /api/v1/surplus \
-H "Authorization: Bearer {driver_token}" \
-H "Content-Type: application/json" \
-d '{"availableLiters": 5000, "totalAmount": 100, "latitude": 10.487, "longitude": -66.88, "address": "Av. Principal", "durationMinutes": 60}'
# Client: reserve a listing (cash)
curl -X POST /api/v1/surplus/5/reserve \
-H "Authorization: Bearer {client_token}" \
-H "Content-Type: application/json" \
-d '{"latitude": 10.49, "longitude": -66.85, "address": "Calle 2, Casa 4"}'