Desarrolladores
API pública
Conectá tu sistema (ERP, BI, CRM o tu propio portal) con la agenda del negocio. La API es por organización, con tokens server-side, scopes y límites de uso. La primera fase es de solo lectura.
1 · Obtené un token
Mientras llega la pantalla de Ajustes → API, el token se emite por consola. Se muestra una sola vez: guardalo en tu servidor.
npm run api:token -- crear --org <slug> --nombre "ERP" --scopes appointments:read,services:read
npm run api:token -- listar --org <slug>
npm run api:token -- revocar --org <slug> --id <id>Los tokens son server-side y de una sola organización: no los embebas en el navegador ni en apps móviles públicas.
2 · Primera llamada
/me devuelve la organización, los scopes del token y el estado de la cuota.
curl https://app.agendese.online/api/v1/me \
-H "Authorization: Bearer agd_<prefijo>_<secreto>"{
"organizacion": { "id": "org_…", "slug": "soluz", "name": "Soluz", "timezone": "America/Asuncion", "currency": "PYG" },
"token": { "nombre": "ERP", "prefijo": "agd_live_ab12", "scopes": ["appointments:read"], "expiraEn": null },
"rateLimit": { "limit": 120, "remaining": 119, "reset": 60 }
}3 · Scopes
Cada endpoint exige su scope; sin comodines. Si falta, la respuesta es 403 con el scope requerido.
| Scope | Permite | Fase |
|---|---|---|
appointments:read | Citas y disponibilidad | F1 |
clients:read | Clientes (allowlist, sin clínicos) | F1 |
services:read | Catálogo de servicios | F1 |
appointments:write | Crear, reprogramar y cancelar citas | F3 |
clients:write | Alta y edición de clientes | F3 |
sales:read | Ventas y pagos | F2 |
webhooks:manage | Webhooks salientes | F2 |
4 · Endpoints
| Método | Ruta | Scope | Estado |
|---|---|---|---|
| GET | /api/v1/me | — | Disponible |
| GET | /api/v1/appointments | appointments:read | En camino |
| POST | /api/v1/appointments | appointments:write | F3 |
| GET | /api/v1/appointments/{id} | appointments:read | En camino |
| GET | /api/v1/availability | appointments:read | En camino |
| GET | /api/v1/clients | clients:read | En camino |
| POST | /api/v1/clients | clients:write | F3 |
| GET | /api/v1/clients/{id} | clients:read | En camino |
| GET | /api/v1/services | services:read | En camino |
| GET | /api/v1/sales | sales:read | F2 |
Los recursos de clientes salen por allowlist y sin datos clínicos. El detalle de parámetros y respuestas de abajo se genera del OpenAPI.
GET /api/v1/me — Introspección del token
Límite: 120/min
Respuestas: 200 Token válido · 401 Token ausente, inválido, vencido o revocado · 429 Rate limit excedido (usar `Retry-After`)
GET /api/v1/appointments — Listar citas de la organización(En camino)
Scopes: appointments:read
Límite: 120/min
| Parámetro | En | Obligatorio | Descripción |
|---|---|---|---|
from | query | Sí | — |
to | query | Sí | — |
status | query | No | — |
staffId | query | No | — |
branchId | query | No | — |
limit | query | No | Tamaño de página. |
cursor | query | No | Cursor opaco devuelto en `next`. |
Respuestas: 200 Página de citas · 401 Token ausente, inválido, vencido o revocado · 403 Falta el scope requerido (viene en `details.scope`) · 429 Rate limit excedido (usar `Retry-After`)
POST /api/v1/appointments — Crear cita (F3, idempotente + atribución D6)(F3)
Scopes: appointments:write
Límite: 120/min
| Parámetro | En | Obligatorio | Descripción |
|---|---|---|---|
Idempotency-Key | header | Sí | — |
Respuestas: 201 Cita creada · 401 Token ausente, inválido, vencido o revocado · 403 Falta el scope requerido (viene en `details.scope`) · 409 Cupo tomado o idempotencia con cuerpo distinto (F3) · 422 Parámetros inválidos
GET /api/v1/appointments/{id} — Detalle de una cita(En camino)
Scopes: appointments:read
Límite: 120/min
| Parámetro | En | Obligatorio | Descripción |
|---|---|---|---|
id | path | Sí | — |
Respuestas: 200 Cita · 401 Token ausente, inválido, vencido o revocado · 403 Falta el scope requerido (viene en `details.scope`) · 404 Recurso inexistente para la organización del token
GET /api/v1/availability — Cupos disponibles(En camino)
Scopes: appointments:read
Límite: 20/min
| Parámetro | En | Obligatorio | Descripción |
|---|---|---|---|
serviceId | query | Sí | — |
branchId | query | Sí | — |
from | query | Sí | — |
to | query | Sí | — |
staffId | query | No | — |
Respuestas: 200 Días con cupos · 401 Token ausente, inválido, vencido o revocado · 403 Falta el scope requerido (viene en `details.scope`) · 429 Rate limit excedido (usar `Retry-After`)
GET /api/v1/clients — Listar clientes (allowlist, sin datos clínicos)(En camino)
Scopes: clients:read
Límite: 120/min
| Parámetro | En | Obligatorio | Descripción |
|---|---|---|---|
q | query | No | Búsqueda por nombre o teléfono |
limit | query | No | Tamaño de página. |
cursor | query | No | Cursor opaco devuelto en `next`. |
Respuestas: 200 Página de clientes · 401 Token ausente, inválido, vencido o revocado · 403 Falta el scope requerido (viene en `details.scope`)
POST /api/v1/clients — Alta de cliente (F3)(F3)
Scopes: clients:write
Límite: 120/min
| Parámetro | En | Obligatorio | Descripción |
|---|---|---|---|
Idempotency-Key | header | Sí | — |
Respuestas: 201 Cliente creado · 401 Token ausente, inválido, vencido o revocado · 403 Falta el scope requerido (viene en `details.scope`) · 422 Parámetros inválidos
GET /api/v1/clients/{id} — Detalle de un cliente (allowlist)(En camino)
Scopes: clients:read
Límite: 120/min
| Parámetro | En | Obligatorio | Descripción |
|---|---|---|---|
id | path | Sí | — |
Respuestas: 200 Cliente · 401 Token ausente, inválido, vencido o revocado · 403 Falta el scope requerido (viene en `details.scope`) · 404 Recurso inexistente para la organización del token
GET /api/v1/services — Catálogo de servicios(En camino)
Scopes: services:read
Límite: 120/min
Respuestas: 200 Servicios activos · 401 Token ausente, inválido, vencido o revocado · 403 Falta el scope requerido (viene en `details.scope`)
GET /api/v1/sales — Ventas y pagos (F2)(F2)
Scopes: sales:read
Límite: 120/min
| Parámetro | En | Obligatorio | Descripción |
|---|---|---|---|
from | query | Sí | — |
to | query | Sí | — |
limit | query | No | Tamaño de página. |
cursor | query | No | Cursor opaco devuelto en `next`. |
Respuestas: 200 Página de ventas · 401 Token ausente, inválido, vencido o revocado · 403 Falta el scope requerido (viene en `details.scope`)
5 · Errores y límites
| HTTP | Código | Cuándo |
|---|---|---|
| 401 | no_autenticado | Token ausente, inválido, vencido o revocado. |
| 403 | scope_insuficiente | Falta el scope del endpoint (viaja en details). |
| 404 | no_encontrado | El recurso no existe para tu organización. |
| 409 | conflicto | Cupo tomado o idempotencia con cuerpo distinto (F3). |
| 422 | validacion | Parámetros inválidos. |
| 429 | cuota | Rate limit: usá Retry-After. |
| 500 | interno | Reintentá con backoff. |
Límite de 120 req/min por token (20/min en disponibilidad), con headers X-RateLimit-Limit/Remaining/Reset y Retry-After al recibir 429. Ante errores 5xx, reintentá con backoff exponencial.
6 · Versionado
La versión mayor viaja en la ruta (/api/v1). Los cambios aditivos no rompen; los cambios incompatibles llegan como /api/v2 con aviso previo y headers de deprecación. El spec OpenAPI está publicado en /api/v1/openapi.json.
7 · Ejemplo en JavaScript
Con fetch (Node 18+) y reintento simple si la respuesta es 429:
const token = process.env.AGENDESE_API_TOKEN
const res = await fetch('https://app.agendese.online/api/v1/me', {
headers: { Authorization: 'Bearer ' + token },
})
if (res.status === 429) {
const espera = Number(res.headers.get('Retry-After') || 1) * 1000
// reintentar después de "espera"
}
if (!res.ok) throw new Error('API ' + res.status)
const me = await res.json()
console.log(me.organizacion.slug, me.token.scopes)8 · Preguntas frecuentes
- ¿Puedo usar el token en el navegador? No: es server-side y accede a los datos de tu organización.
- ¿401 o 403? 401 = token ausente, inválido, vencido o revocado; 403 = falta el scope del endpoint.
- ¿Qué hago con un 429? Respetá
Retry-Aftery los headersX-RateLimit-*; aplicá backoff y caché de lecturas. - ¿Hay escrituras? Todavía no: F1 es lectura. F3 suma crear y cancelar con idempotencia y atribución de origen.
- ¿Devuelve datos clínicos? Nunca: la salud va aparte y los clientes salen por allowlist.
¿Solo necesitás mostrar turnos en tu web? El widget embebible no necesita API: Insertar el widget de reservas.
Lo que todavía no hace
- Escrituras (F3): crear o cancelar citas por API, con idempotencia y atribución de origen.
- Webhooks (F2): avisos firmados para sincronizar sin polling.
- Datos clínicos: nunca en la API pública.
- Panel de tokens (ui): mientras tanto, la emisión es por consola.
¿Necesitás una integración? Escribinos a [email protected] y lo vemos con el equipo.