API pública de la Plataforma Ciudadana (1.0.0)

Download OpenAPI specification:

API de solo lectura de la Plataforma Ciudadana. Publica el catálogo de unidades municipales y categorías de reporte, y los reportes que la ciudadanía hace por WhatsApp o por el centro de llamadas.

Todas las operaciones son GET y responden JSON.

Autenticación

Cada solicitud a /v1 lleva la clave en la cabecera X-API-Key:

curl -H "X-API-Key: pcd_…" https://<servidor>/v1/unities

El municipio emite una clave por consumidor. Es un secreto: va en el servidor que llama a la API, no en el código de una página web, donde cualquiera la puede leer. Una clave revocada deja de funcionar en a lo sumo un minuto.

Límites

Cada clave tiene un cupo de solicitudes por minuto. Las respuestas informan el estado en las cabeceras RateLimit y RateLimit-Policy; al superarlo, la API responde 429 con Retry-After, en segundos.

Formato de respuesta

Una respuesta exitosa trae el resultado en data. Un listado paginado agrega nextCursor:

{
  "data": [{ "unityId": 3, "name": "Aseo Municipal", "description": "…" }]
}

Errores

Los errores siguen Problem Details (RFC 9457) con Content-Type: application/problem+json. Un 400 lista en errors cada parámetro con problema, todos a la vez:

{
  "type": "about:blank",
  "title": "Bad Request",
  "status": 400,
  "detail": "Uno o más parámetros son inválidos.",
  "instance": "/v1/reports",
  "errors": [
    { "parameter": "status", "message": "estado no reconocido" },
    { "parameter": "from", "message": "debe ser una fecha yyyy-MM-dd válida" }
  ]
}
Estado Cuándo
400 Un parámetro no cumple su formato o no existe. Un parámetro mal escrito no se ignora: unityid en vez de unityId es un error.
401 Falta X-API-Key, o la clave no es válida o fue revocada.
404 El recurso no existe o no es público.
429 Se superó el límite de solicitudes. Reintentar después de los segundos que indica Retry-After.
503 El servicio no puede leer los datos en este momento. Conviene reintentar con espera creciente.

Un 503 o un 500 trae en detail una referencia para soporte. Cada respuesta lleva además el encabezado x-request-id; si la solicitud envía uno propio, se respeta.

Caché

Las respuestas llevan Cache-Control y ETag. Una solicitud con If-None-Match recibe 304 si el contenido no cambió. El catálogo se cachea cinco minutos y los reportes, uno.

Privacidad

Ningún endpoint publica datos de quien hizo un reporte: ni nombre, ni teléfono, ni fotos. Las coordenadas se redondean a unos 110 m y los reportes que el municipio rechazó no se publican.

Listar unidades

Unidades municipales activas, ordenadas por unityId.

Authorizations:
ApiKey

Responses

Response samples

Content type
application/json
{
  • "data": [
    ]
}

Obtener una unidad

Authorizations:
ApiKey
path Parameters
unityId
required
integer >= 0
Example: 3

Identificador de la unidad.

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Listar categorías

Categorías activas, ordenadas por unidad y categoryId.

Authorizations:
ApiKey
query Parameters
unityId
integer >= 0
Example: unityId=3

Solo las categorías de esta unidad.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ]
}

Obtener una categoría

Authorizations:
ApiKey
path Parameters
categoryId
required
integer >= 0
Example: 12

Identificador de la categoría.

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}

Reportes

Reportes ciudadanos individuales, los mismos que muestra el mapa público del municipio.

Filtros

Los filtros se combinan entre sí. Por ejemplo, los reportes abiertos o en atención de la unidad 3 durante octubre:

GET /v1/reports?unityId=3&status=open,in_progress&from=2026-10-01&to=2026-10-31

from y to son días de calendario en la zona horaria del municipio, ambos inclusivos. to=2026-10-31 incluye todo el 31 de octubre.

Paginación

El listado va del reporte más reciente al más antiguo y se pagina por cursor. Si nextCursor no es null, hay más reportes: se pide la página siguiente repitiendo los mismos filtros y pasando ese valor en cursor.

GET /v1/reports?unityId=3&limit=50
GET /v1/reports?unityId=3&limit=50&cursor=WzE3OTI...

El cursor marca una posición, no un número de página: un reporte que entra mientras se recorre el listado no corre las páginas ya leídas ni hace que un reporte salga dos veces. Es opaco; no conviene armarlo ni interpretarlo.

Buscar reportes

Reportes ciudadanos, del más reciente al más antiguo. Los filtros se combinan entre sí.

La paginación es por cursor: si nextCursor no es null, se pasa tal cual como cursor con los mismos filtros para pedir la página siguiente. Un reporte nuevo no corre las páginas ya leídas, como sí pasaría con un desplazamiento numérico.

Los reportes rechazados por el municipio no se publican.

Authorizations:
ApiKey
query Parameters
categoryId
integer >= 0
Example: categoryId=12

Solo los reportes de esta categoría.

unityId
integer >= 0
Example: unityId=3

Solo los reportes de esta unidad.

status
Array of strings non-empty
Items Enum: "open" "in_progress" "resolved" "closed"
Example: status=open&status=in_progress

Uno o varios estados, separados por coma o repitiendo el parámetro.

from
string <date>
Example: from=2026-10-01

Creados desde este día inclusive, en la zona horaria del municipio.

to
string <date>
Example: to=2026-10-31

Creados hasta este día inclusive, en la zona horaria del municipio.

limit
integer [ 1 .. 100 ]
Default: 25
Example: limit=25

Reportes por página.

cursor
string [ 1 .. 200 ] characters

nextCursor de la página anterior. Se omite en la primera.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "nextCursor": "string"
}

Obtener un reporte

Authorizations:
ApiKey
path Parameters
reportId
required
string^[0-9a-fA-F]{24}$
Example: 6650f1c2a9e4b3d2c1f0e9a8

Identificador del reporte.

Responses

Response samples

Content type
application/json
{
  • "data": {
    }
}