Centro de ayuda

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.

ScopePermiteFase
appointments:readCitas y disponibilidadF1
clients:readClientes (allowlist, sin clínicos)F1
services:readCatálogo de serviciosF1
appointments:writeCrear, reprogramar y cancelar citasF3
clients:writeAlta y edición de clientesF3
sales:readVentas y pagosF2
webhooks:manageWebhooks salientesF2

4 · Endpoints

MétodoRutaScopeEstado
GET/api/v1/me—Disponible
GET/api/v1/appointmentsappointments:readEn camino
POST/api/v1/appointmentsappointments:writeF3
GET/api/v1/appointments/{id}appointments:readEn camino
GET/api/v1/availabilityappointments:readEn camino
GET/api/v1/clientsclients:readEn camino
POST/api/v1/clientsclients:writeF3
GET/api/v1/clients/{id}clients:readEn camino
GET/api/v1/servicesservices:readEn camino
GET/api/v1/salessales:readF2

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ámetroEnObligatorioDescripción
fromquerySí—
toquerySí—
statusqueryNo—
staffIdqueryNo—
branchIdqueryNo—
limitqueryNoTamaño de página.
cursorqueryNoCursor 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ámetroEnObligatorioDescripción
Idempotency-KeyheaderSí—

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ámetroEnObligatorioDescripción
idpathSí—

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ámetroEnObligatorioDescripción
serviceIdquerySí—
branchIdquerySí—
fromquerySí—
toquerySí—
staffIdqueryNo—

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ámetroEnObligatorioDescripción
qqueryNoBúsqueda por nombre o teléfono
limitqueryNoTamaño de página.
cursorqueryNoCursor 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ámetroEnObligatorioDescripción
Idempotency-KeyheaderSí—

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ámetroEnObligatorioDescripción
idpathSí—

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ámetroEnObligatorioDescripción
fromquerySí—
toquerySí—
limitqueryNoTamaño de página.
cursorqueryNoCursor 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

HTTPCódigoCuándo
401no_autenticadoToken ausente, inválido, vencido o revocado.
403scope_insuficienteFalta el scope del endpoint (viaja en details).
404no_encontradoEl recurso no existe para tu organización.
409conflictoCupo tomado o idempotencia con cuerpo distinto (F3).
422validacionParámetros inválidos.
429cuotaRate limit: usá Retry-After.
500internoReintentá 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-After y los headers X-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.

Portal de reservas · Los mensajes de WhatsApp se preparan con enlace `wa.me`; no se afirma envío, entrega ni lectura.