Back-office management of filling stations (water and fuel): listing, CRUD, status
toggling, report/comment moderation, and bulk CSV import. Paths are relative to the
/api/v1 prefix; these are back-office endpoints that require an admin token.
Requires an admin bearer token (see Admin auth). Admin only.
The admin station API speaks Spanish:
tipoisAGUA/GASOLINA,estadoisACTIVA/INACTIVA(the list filter accepts lowercaseactiva/inactiva/todas), and report colors areverde/amarillo/rojo/cerrada.
See Common errors and the public Stations endpoints.
| Method | URI | Access | Description |
|---|---|---|---|
GET |
/admin/stations/bulk-import/template |
Admin | Download the CSV import template |
POST |
/admin/stations/bulk-import |
Admin | Bulk import stations from a CSV |
GET |
/admin/stations/bulk-import/{jobId} |
Admin | Status of an import job |
GET |
/admin/stations |
Admin | Paginated station list (cursor-based) |
POST |
/admin/stations |
Admin | Create a station |
GET |
/admin/stations/{stationId} |
Admin | Station detail with recent reports |
PATCH |
/admin/stations/{stationId} |
Admin | Update a station |
PATCH |
/admin/stations/{stationId}/status |
Admin | Activate / deactivate a station |
DELETE |
/admin/stations/{stationId} |
Admin | Soft-delete a station |
GET |
/admin/stations/{stationId}/reports |
Admin | Paginated reports (cursor-based) |
GET |
/admin/stations/{stationId}/comments |
Admin | Paginated comments (cursor-based) |
DELETE |
/admin/stations/{stationId}/comments/{commentId} |
Admin | Moderate (delete) a comment |
Downloads the CSV import template (text/csv, plantilla_estaciones.csv); there is
no JSON body. The header row is name, address, latitude, longitude, tipo, description, landmark, city, opening_time, closing_time (the first five are
required). Pass con_ejemplos=true to include three sample rows.
Query
| Field | Type | Required | Rules |
|---|---|---|---|
con_ejemplos |
boolean | no | include example rows (default false) |
Response 200 — a text/csv stream.
Uploads a CSV (multipart/form-data) to create or update stations. Up to 100 rows
return a report (200); larger files (up to 2,000 rows) return a job id (202) to
poll.
Request body
| Field | Type | Required | Rules |
|---|---|---|---|
file |
file | yes | csv/txt, ≤ 5 MB |
Response 200 (≤ 100 rows)
{
"report": {
"processed": 80,
"created": 72,
"updated": 5,
"skipped": 3,
"skipped_rows": [
{ "line": 14, "reason": "Coordenadas fuera de Venezuela." }
]
}
}
Response 202 (more than 100 rows)
{ "job_id": 9, "status": "PENDING", "total_rows": 540 }
Errors
| Status | Body | When |
|---|---|---|
422 |
{ "error": "InvalidEncoding", "message": "…" } |
File is not UTF-8 |
422 |
{ "error": "EmptyFile", "message": "…" } |
File is empty |
422 |
{ "error": "MissingHeaders", "message": "…" } |
Required headers missing |
422 |
{ "error": "TooManyRows", "message": "…" } |
More than 2,000 rows |
Polls the status of an import job. Only the admin who launched the job may read it.
Response 200
{
"job_id": 9,
"status": "COMPLETED",
"total_rows": 540,
"report": {
"processed": 540,
"created": 500,
"updated": 30,
"skipped": 10,
"skipped_rows": [ { "line": 22, "reason": "Tipo inválido: 'X' (use AGUA o GASOLINA)." } ]
},
"error": null,
"created_at": "2026-06-26T12:00:00+00:00",
"updated_at": "2026-06-26T12:01:10+00:00"
}
report and error are null until the job finishes.
Errors
| Status | Body | When |
|---|---|---|
404 |
{ "error": "ImportJobNotFound" } |
Unknown job id |
403 |
{ "error": "Forbidden", "message": "…" } |
The job belongs to another admin |
Cursor-paginated station list, newest first. See Pagination → cursor-based.
Query
| Field | Type | Required | Rules |
|---|---|---|---|
tipo |
string | no | AGUA or GASOLINA |
estado |
string | no | activa, inactiva, or todas (default todas) |
q |
string | no | ≤ 120 chars; matches name or address |
limit |
integer | no | 1–100, default 25 |
cursor |
string | no | opaque cursor from next_cursor |
Response 200
{
"items": [
{
"id": 5,
"name": "Pozo La Trinidad",
"address": "Av. Principal, Caracas",
"tipo": "AGUA",
"estado": "ACTIVA",
"last_report_at": "2026-06-26T11:00:00+00:00"
}
],
"next_cursor": "eyJpZCI6NH0",
"has_more": true
}
Creates a station. Coordinates must fall within Venezuela, and a station with the
same name within 50 m is rejected as a duplicate. New stations start ACTIVA.
Request body
| Field | Type | Required | Rules |
|---|---|---|---|
name |
string | yes | ≤ 160 chars |
address |
string | yes | ≤ 280 chars |
latitude |
number | yes | between −90 and 90 |
longitude |
number | yes | between −180 and 180 |
tipo |
string | yes | AGUA or GASOLINA |
description |
string | no | ≤ 1000 chars |
landmark |
string | no | ≤ 280 chars |
city |
string | no | ≤ 120 chars; defaults to Caracas |
opening_time |
string | no | HH:MM |
closing_time |
string | no | HH:MM |
Response 201 — { "station": { … } } (the detail object, see below).
Errors
| Status | Body | When |
|---|---|---|
422 |
{ "error": "CoordsOutOfRange", "message": "…" } |
Coordinates outside Venezuela |
422 |
{ "error": "DuplicateStation", "message": "…" } |
Same name within 50 m exists |
Station detail with its reports from the last 30 days and an aggregate summary.
Response 200
{
"station": {
"id": 5,
"name": "Pozo La Trinidad",
"address": "Av. Principal, Caracas",
"description": "Pozo comunitario",
"landmark": "Frente a la plaza",
"latitude": 10.4806,
"longitude": -66.9036,
"city": "Caracas",
"tipo": "AGUA",
"estado": "ACTIVA",
"is_verified": true,
"opening_time": "06:00",
"closing_time": "18:00",
"created_at": "2026-06-01T08:00:00+00:00",
"updated_at": "2026-06-26T11:00:00+00:00"
},
"reports": [
{
"id": 31,
"color": "verde",
"wait_minutes": 10,
"comment": "Cola corta",
"reporter": "Carlos Rodríguez",
"created_at": "2026-06-26T11:00:00+00:00"
}
],
"summary": {
"total_reports_30d": 12,
"avg_wait_minutes": 18,
"color_breakdown": { "verde": 7, "amarillo": 3, "rojo": 2, "cerrada": 0 }
}
}
reporter is null for reports with no identifiable author.
Errors
| Status | Body | When |
|---|---|---|
404 |
{ "error": "StationNotFound" } |
Unknown station id |
Partial update. Any omitted field is left unchanged. Moved coordinates must fall within Venezuela.
Request body
| Field | Type | Required | Rules |
|---|---|---|---|
name |
string | no | ≤ 160 chars |
address |
string | no | ≤ 280 chars |
latitude |
number | no | between −90 and 90 |
longitude |
number | no | between −180 and 180 |
tipo |
string | no | AGUA or GASOLINA |
description |
string|null | no | ≤ 1000 chars |
landmark |
string|null | no | ≤ 280 chars |
city |
string | no | ≤ 120 chars |
opening_time |
string|null | no | HH:MM |
closing_time |
string|null | no | HH:MM |
Response 200 — { "station": { … } } (the detail object).
Errors
| Status | Body | When |
|---|---|---|
404 |
{ "error": "StationNotFound" } |
Unknown station id |
422 |
{ "error": "CoordsOutOfRange", "message": "…" } |
Moved coordinates outside Venezuela |
Activates or deactivates a station.
Request body
| Field | Type | Required | Rules |
|---|---|---|---|
estado |
string | yes | ACTIVA or INACTIVA |
Response 200
{ "id": 5, "estado": "INACTIVA", "updated_at": "2026-06-29T10:00:00+00:00" }
Errors
| Status | Body | When |
|---|---|---|
404 |
{ "error": "StationNotFound" } |
Unknown station id |
Deletes a station. Blocked when the station has any report in the last 7 days — deactivate it instead.
Response 200
{ "deleted": true }
Errors
| Status | Body | When |
|---|---|---|
404 |
{ "error": "StationNotFound" } |
Unknown station id |
422 |
{ "error": "HasRecentReports", "message": "…" } |
Station has reports in the last 7 days |
Cursor-paginated report history for a station, newest first. See Pagination → cursor-based.
Query
| Field | Type | Required | Rules |
|---|---|---|---|
color |
string | no | verde, amarillo, rojo, or cerrada |
from |
date | no | filter created_at >= |
to |
date | no | filter created_at <=; >= from |
limit |
integer | no | 1–100, default 25 |
cursor |
string | no | opaque cursor from next_cursor |
Response 200
{
"items": [
{
"id": 31,
"color": "verde",
"wait_minutes": 10,
"comment": "Cola corta",
"reporter": "Carlos Rodríguez",
"created_at": "2026-06-26T11:00:00+00:00"
}
],
"next_cursor": "eyJpZCI6MzB9",
"has_more": true
}
Errors
| Status | Body | When |
|---|---|---|
404 |
{ "error": "StationNotFound" } |
Unknown station id |
Cursor-paginated comment wall for a station (for moderation), newest first. See Pagination → cursor-based.
Query
| Field | Type | Required | Rules |
|---|---|---|---|
limit |
integer | no | 1–100, default 25 |
cursor |
string | no | opaque cursor from next_cursor |
Response 200
{
"items": [
{
"id": 12,
"author": { "id": 3, "name": "Carlos Rodríguez", "role": "DRIVER" },
"content": "El pozo cerró temprano hoy.",
"created_at": "2026-06-26T11:30:00+00:00"
}
],
"next_cursor": "eyJpZCI6MTF9",
"has_more": true
}
Errors
| Status | Body | When |
|---|---|---|
404 |
{ "error": "StationNotFound" } |
Unknown station id |
Moderation: deletes any comment on the station's wall.
Response 200
{ "ok": true }
Errors
| Status | Body | When |
|---|---|---|
404 |
{ "error": "CommentNotFound" } |
No such comment on this station |