Saltar al contenido

Desarrolladores

API de Kalmee

Conecta la cola y la cita previa con los programas que ya usa tu organización: el gestor de expedientes, la sede electrónica, la app municipal o la cartelería.

Versión 1.0.0 · REST y JSON

Para qué sirve

  • Gestor de expedientes: reservar y anular citas desde el expediente y consultar las del día.
  • Sede electrónica: enseñar los huecos libres de un trámite y reservar sin salir de la sede.
  • App municipal o cartelería: mostrar la cola en directo y dar un número antes de llegar.
  • Cuadros de mando: llevar las estadísticas de atención a tu herramienta de informes.

Primeros pasos

  1. Crea una clave en Kalmee → Integraciones (API). Pueden hacerlo la propiedad y los administradores de la organización.
  2. Guárdala en el programa que la va a usar, como una contraseña: Kalmee solo la enseña una vez.
  3. Comprueba que funciona pidiendo los datos de la clave:
export KALMEE_API_KEY="kal_live_…"
curl "https://api.kalmee.com/v1/me" \
  -H "Authorization: Bearer $KALMEE_API_KEY"

Autenticación y permisos

Todas las peticiones llevan la clave en la cabecera Authorization, nunca en la dirección (URL).

Authorization: Bearer kal_live_a1b2c3d4_…

Las claves empiezan por kal_live_, siguen con un prefijo de 8 caracteres y terminan con el secreto. El prefijo identifica la clave en Kalmee y en el registro de actividad; el secreto solo lo ve quien crea la clave.

Cada clave tiene uno o varios alcances:

  • Alcance: readPermite: Leer oficinas, trámites, la cola en directo, los huecos libres, las citas y las estadísticas.
  • Alcance: queue:writePermite: Dar números sin cita, reservar citas y anularlas. No incluye leer: añade también read si lo necesitas.
  • Alcance: adminPermite: Todo lo anterior y, más adelante, la configuración.

Pueden crear claves la propiedad y los administradores; el alcance admin solo lo puede dar la propiedad. Una clave revocada deja de funcionar al momento.

Límites de peticiones

Cada clave puede hacer 120 peticiones por minuto. Cada respuesta lleva estas cabeceras:

  • RateLimit-Limit: peticiones permitidas por minuto.
  • RateLimit-Remaining: las que quedan en este minuto.
  • RateLimit-Reset: segundos hasta que el contador vuelve a empezar.

Si se pasa del límite, la respuesta es 429 con la cabecera Retry-After: los segundos que hay que esperar.

Errores

Los errores siguen el formato application/problem+json (RFC 9457). El campo code no cambia nunca ni se traduce: es el que tiene que mirar tu programa.

Ejemplo:

HTTP/1.1 409 Conflict
Content-Type: application/problem+json

{
  "type": "https://kalmee.com/desarrolladores#error-slot_full",
  "title": "El hueco ya está completo",
  "status": 409,
  "code": "slot_full",
  "detail": "No quedan plazas a las 09:20 para «Padrón».",
  "instance": "/api/v1/appointments#req_3f9c…"
}
  • code: unauthorizedHTTP: 401Significado: Falta la clave de API
  • code: invalid_keyHTTP: 401Significado: La clave de API no es válida
  • code: key_revokedHTTP: 401Significado: La clave de API está revocada
  • code: key_expiredHTTP: 401Significado: La clave de API ha caducado
  • code: insufficient_scopeHTTP: 403Significado: La clave no tiene el permiso necesario
  • code: plan_requiredHTTP: 403Significado: El plan de la organización no incluye la API
  • code: not_foundHTTP: 404Significado: No existe o no es de tu organización
  • code: invalid_jsonHTTP: 400Significado: El cuerpo no es JSON válido
  • code: idempotency_key_requiredHTTP: 400Significado: Falta la cabecera Idempotency-Key
  • code: validation_failedHTTP: 422Significado: Los datos no son válidos
  • code: idempotency_key_reusedHTTP: 422Significado: La Idempotency-Key ya se usó con otro cuerpo
  • code: invalid_targetHTTP: 422Significado: El tipo de ticket no es de ese trámite u oficina
  • code: request_in_progressHTTP: 409Significado: La petición con esa Idempotency-Key aún está en curso
  • code: service_unavailableHTTP: 409Significado: El trámite no está disponible
  • code: office_closedHTTP: 409Significado: La oficina está cerrada ahora
  • code: walkin_limit_reachedHTTP: 409Significado: Se ha llegado al límite diario de tickets de ese trámite
  • code: slot_unavailableHTTP: 409Significado: Ese hueco no está libre
  • code: slot_fullHTTP: 409Significado: El hueco ya está completo
  • code: duplicate_bookingHTTP: 409Significado: Esa persona ya tiene una cita en ese trámite
  • code: invalid_stateHTTP: 409Significado: La cita no se puede cambiar en su estado actual
  • code: rate_limitedHTTP: 429Significado: Demasiadas peticiones
  • code: not_configuredHTTP: 503Significado: La API no está configurada en este servidor
  • code: internal_errorHTTP: 500Significado: Error interno

Cada respuesta lleva la cabecera X-Request-Id. Indícala si escribes a soporte.

Idempotencia

Todas las peticiones POST necesitan la cabecera Idempotency-Key: un UUID nuevo para cada operación.

Idempotency-Key: 5d8e1c0a-3b7f-4a52-9d61-2f0c8e4b7a19

Si la conexión falla, repite la petición con la misma clave: no se dará un segundo número ni se reservará dos veces. Durante 24 horas se devuelve la misma respuesta, con la cabecera Idempotent-Replayed: true.

Con la misma Idempotency-Key y otro cuerpo, la respuesta es 422 idempotency_key_reused. Si la primera petición sigue en curso, 409 request_in_progress.

Datos personales

La API nunca devuelve el nombre, el correo, el teléfono ni el DNI de las personas: las citas y la cola solo llevan identificadores, horas, estados y códigos de ticket.

Para reservar una cita sí hay que enviar los datos de la persona. Tu programa se encarga de informarle y de recoger su consentimiento, como hace la página de cita previa. Kalmee los guarda el tiempo que tenga configurado la organización y después los borra.

Todo lo que hace una clave queda en el registro de actividad de la organización, con el nombre de la clave.

Referencia

Dirección base: https://api.kalmee.com/v1. Las respuestas correctas van dentro de data; los listados paginados añaden meta.

La misma API sigue respondiendo en https://app.kalmee.com/api/v1, así que las integraciones que ya la usan no tienen que cambiar nada.

Las oficinas llevan su código DIR3 (dir3_code) y los trámites su código SIA (sia_code). Para encontrarlos sin guardar identificadores, filtra con ?dir3= en /offices y con ?sia= en /offices/…/services.

Cuenta

get/me· Organización y clave

La organización de la clave, sus alcances y su límite de peticiones. Sirve para comprobar que la clave funciona.

Alcance: read

Petición de ejemplo
curl "https://api.kalmee.com/v1/me" \
  -H "Authorization: Bearer $KALMEE_API_KEY"
Respuesta de ejemplo · 200
{
  "data": {
    "organization": {
      "id": "7a0c5f3e-2b1d-4c8e-9f6a-1d2e3f4a5b6c",
      "name": "Ajuntament de Vilamar",
      "slug": "vilamar",
      "dir3_code": "L01030140",
      "timezone": "Europe/Madrid",
      "locale_default": "ca"
    },
    "key": {
      "id": "3c5e7a9b-1d3f-4b5d-8e7f-9a1b3c5d7e9f",
      "name": "Gestor de expedientes",
      "prefix": "a1b2c3d4",
      "scopes": [
        "read",
        "queue:write"
      ]
    },
    "rate_limit": {
      "per_minute": 120
    }
  }
}

get/openapi.json· Este documento

El documento OpenAPI 3.1 de la API. Es público y no lleva datos.

Alcance: Público, sin clave

Petición de ejemplo
curl "https://api.kalmee.com/v1/openapi.json"
Respuesta de ejemplo · 200
{
  "openapi": "3.1.0",
  "info": {
    "title": "API de Kalmee",
    "version": "1.0.0"
  }
}

Oficinas

get/offices· Lista de oficinas

Todas las oficinas de la organización, por nombre. Con `dir3`, solo la oficina que tiene ese código DIR3 (o ninguna).

Alcance: read

Parámetros
  • activeconsulta · OpcionalSolo las activas (true) o las inactivas (false).
  • dir3consulta · OpcionalSolo la oficina con este código DIR3 (mayúsculas o minúsculas).
Petición de ejemplo
curl "https://api.kalmee.com/v1/offices?active=true" \
  -H "Authorization: Bearer $KALMEE_API_KEY"
Respuesta de ejemplo · 200
{
  "data": [
    {
      "id": "c41d8e2a-6b7f-4a1c-9d3e-5f6a7b8c9d0e",
      "name": "Oficina de Atención Ciudadana",
      "address": "Plaça Major, 1",
      "dir3_code": "LA0012345",
      "timezone": "Europe/Madrid",
      "desk_word": "mesa",
      "active": true,
      "appointment_window": {
        "before_min": 10,
        "after_min": 15
      },
      "virtual_queue_enabled": true
    }
  ]
}

Errores de esta operación: validation_failed

get/offices/{office_id}· Una oficina

Los datos de una oficina y si está abierta ahora (según su horario, los festivos y las jornadas especiales).

Alcance: read

Parámetros
  • office_idruta · ObligatorioIdentificador de la oficina.
Petición de ejemplo
curl "https://api.kalmee.com/v1/offices/c41d8e2a-6b7f-4a1c-9d3e-5f6a7b8c9d0e" \
  -H "Authorization: Bearer $KALMEE_API_KEY"
Respuesta de ejemplo · 200
{
  "data": {
    "id": "c41d8e2a-6b7f-4a1c-9d3e-5f6a7b8c9d0e",
    "name": "Oficina de Atención Ciudadana",
    "address": "Plaça Major, 1",
    "dir3_code": "LA0012345",
    "timezone": "Europe/Madrid",
    "desk_word": "mesa",
    "active": true,
    "appointment_window": {
      "before_min": 10,
      "after_min": 15
    },
    "virtual_queue_enabled": true,
    "open_now": true
  }
}

Errores de esta operación: not_found

get/offices/{office_id}/services· Trámites de una oficina

Los trámites (servicios) de la oficina con su letra, su código SIA, su categoría, prioridad, duración, límite diario y ajustes de cita por internet. Con `sia`, solo el trámite que tiene ese código SIA (o ninguno).

Alcance: read

Parámetros
  • office_idruta · ObligatorioIdentificador de la oficina.
  • siaconsulta · OpcionalSolo el trámite con este código SIA.
Petición de ejemplo
curl "https://api.kalmee.com/v1/offices/c41d8e2a-6b7f-4a1c-9d3e-5f6a7b8c9d0e/services" \
  -H "Authorization: Bearer $KALMEE_API_KEY"
Respuesta de ejemplo · 200
{
  "data": [
    {
      "id": "9f00a1b2-c3d4-4e5f-8a6b-7c8d9e0f1a2b",
      "office_id": "c41d8e2a-6b7f-4a1c-9d3e-5f6a7b8c9d0e",
      "code_prefix": "A",
      "sia_code": "2042730",
      "category": "Padrón",
      "name": {
        "es": "Padrón",
        "ca": "Padró",
        "cat": "Padró",
        "gl": "Padrón",
        "eu": "Errolda"
      },
      "icon": "file-text",
      "priority": 0,
      "duration_min": 10,
      "walkin_daily_limit": 80,
      "requires_id": true,
      "active": true,
      "booking": {
        "enabled": true,
        "min_days_ahead": 1,
        "max_days_ahead": 30,
        "capacity": 2
      }
    }
  ]
}

Errores de esta operación: not_found, validation_failed

Cola

get/offices/{office_id}/queue· Cola en directo

Cuántas personas esperan en cada trámite, cuántas están llamadas o siendo atendidas, las mesas abiertas y la espera estimada para un número nuevo. Sin datos personales: solo los códigos de los tickets.

Alcance: read

Parámetros
  • office_idruta · ObligatorioIdentificador de la oficina.
Petición de ejemplo
curl "https://api.kalmee.com/v1/offices/c41d8e2a-6b7f-4a1c-9d3e-5f6a7b8c9d0e/queue" \
  -H "Authorization: Bearer $KALMEE_API_KEY"
Respuesta de ejemplo · 200
{
  "data": {
    "office_id": "c41d8e2a-6b7f-4a1c-9d3e-5f6a7b8c9d0e",
    "generated_at": "2026-10-20T08:14:03Z",
    "waiting_total": 7,
    "services": [
      {
        "service_id": "9f00a1b2-c3d4-4e5f-8a6b-7c8d9e0f1a2b",
        "code_prefix": "A",
        "name": {
          "es": "Padrón",
          "ca": "Padró",
          "cat": "Padró",
          "gl": "Padrón",
          "eu": "Errolda"
        },
        "waiting": 7,
        "called": 1,
        "serving": 2,
        "open_desks": 3,
        "estimated_wait_min": 24
      }
    ],
    "desks": [
      {
        "desk_id": "55aa6b7c-8d9e-4f0a-b1c2-d3e4f5a6b7c8",
        "number": 4,
        "name": "Mesa 4",
        "status": "open",
        "current": {
          "code": "A023",
          "status": "serving",
          "service_id": "9f00a1b2-c3d4-4e5f-8a6b-7c8d9e0f1a2b"
        }
      }
    ]
  }
}

Errores de esta operación: not_found

post/tickets· Dar un número

Da un número sin cita en un trámite, como el kiosco: misma numeración, mismo límite diario y solo con la oficina abierta. Devuelve el código, las personas por delante, una espera aproximada y el enlace de seguimiento para la persona.

Alcance: queue:write

Cuerpo de la petición
{
  "service_id": "9f00a1b2-c3d4-4e5f-8a6b-7c8d9e0f1a2b"
}
Petición de ejemplo
curl -X POST "https://api.kalmee.com/v1/tickets" \
  -H "Authorization: Bearer $KALMEE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"service_id":"9f00a1b2-c3d4-4e5f-8a6b-7c8d9e0f1a2b"}'
Respuesta de ejemplo · 201
{
  "data": {
    "id": "1b9e2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d",
    "code": "A024",
    "status": "waiting",
    "office_id": "c41d8e2a-6b7f-4a1c-9d3e-5f6a7b8c9d0e",
    "service_id": "9f00a1b2-c3d4-4e5f-8a6b-7c8d9e0f1a2b",
    "priority": 0,
    "issued_at": "2026-10-20T08:15:40Z",
    "ahead": 7,
    "estimated_wait_min": 24,
    "tracking_url": "https://app.kalmee.com/t/4f1c…"
  }
}

Errores de esta operación: validation_failed, idempotency_key_required, idempotency_key_reused, request_in_progress, service_unavailable, office_closed, walkin_limit_reached, invalid_target

Citas

get/availability· Huecos libres

Los huecos de cita libres de un trámite por día, con el mismo calendario que la página de cita previa (horario, festivos, jornadas especiales, antelación y plazas).

Alcance: read

Parámetros
  • service_idconsulta · ObligatorioTrámite con cita por internet.
  • fromconsulta · ObligatorioPrimer día (AAAA-MM-DD), en la zona horaria de la oficina.
  • toconsulta · ObligatorioÚltimo día, incluido. Como mucho 31 días desde «from».
Petición de ejemplo
curl "https://api.kalmee.com/v1/availability?service_id=9f00a1b2-c3d4-4e5f-8a6b-7c8d9e0f1a2b&from=2026-10-20&to=2026-10-24" \
  -H "Authorization: Bearer $KALMEE_API_KEY"
Respuesta de ejemplo · 200
{
  "data": {
    "service_id": "9f00a1b2-c3d4-4e5f-8a6b-7c8d9e0f1a2b",
    "office_id": "c41d8e2a-6b7f-4a1c-9d3e-5f6a7b8c9d0e",
    "timezone": "Europe/Madrid",
    "days": [
      {
        "date": "2026-10-22",
        "slots": [
          {
            "starts_at": "2026-10-22T07:20:00Z",
            "ends_at": "2026-10-22T07:30:00Z",
            "time": "09:20",
            "remaining": 2
          }
        ]
      }
    ]
  }
}

Errores de esta operación: validation_failed, not_found, service_unavailable

get/appointments· Lista de citas

Las citas por hora de inicio, sin el nombre, el correo, el teléfono ni el DNI de la persona. Paginada con cursor: si `meta.has_more` es true, pide la página siguiente con `cursor = meta.next_cursor`. Como mucho 92 días por consulta.

Alcance: read

Parámetros
  • fromconsulta · ObligatorioPrimer día (AAAA-MM-DD), en la zona horaria de la oficina.
  • toconsulta · ObligatorioÚltimo día, incluido. Como mucho 92 días desde «from».
  • office_idconsulta · OpcionalSolo las de esta oficina.
  • statusconsulta · OpcionalSolo las de este estado.
  • limitconsulta · OpcionalCitas por página, de 1 a 200 (por defecto, 50).
  • cursorconsulta · OpcionalEl `meta.next_cursor` de la página anterior.
Petición de ejemplo
curl "https://api.kalmee.com/v1/appointments?from=2026-10-20&to=2026-10-24&office_id=c41d8e2a-6b7f-4a1c-9d3e-5f6a7b8c9d0e&status=booked" \
  -H "Authorization: Bearer $KALMEE_API_KEY"
Respuesta de ejemplo · 200
{
  "data": [
    {
      "id": "e2d4f6a8-b0c2-4d4e-8f6a-8b0c2d4e6f8a",
      "office_id": "c41d8e2a-6b7f-4a1c-9d3e-5f6a7b8c9d0e",
      "service_id": "9f00a1b2-c3d4-4e5f-8a6b-7c8d9e0f1a2b",
      "starts_at": "2026-10-22T07:20:00Z",
      "ends_at": "2026-10-22T07:30:00Z",
      "status": "booked",
      "source": "online",
      "ticket_id": null,
      "created_at": "2026-10-19T17:40:11Z",
      "cancelled_at": null,
      "checked_in_at": null
    }
  ],
  "meta": {
    "next_cursor": null,
    "has_more": false
  }
}

Errores de esta operación: validation_failed, not_found

post/appointments· Reservar una cita

Reserva un hueco para una persona con las mismas comprobaciones que la página de cita previa: el hueco tiene que estar libre y la base de datos garantiza las plazas. Con `notify` (por defecto, true) se envía el correo de confirmación. El consentimiento informado de la persona lo recoge su sistema (sede electrónica, gestor de expedientes, centralita).

Alcance: queue:write

Cuerpo de la petición
{
  "service_id": "9f00a1b2-c3d4-4e5f-8a6b-7c8d9e0f1a2b",
  "starts_at": "2026-10-22T07:20:00Z",
  "citizen": {
    "name": "Marta Soler",
    "email": "marta@example.com",
    "phone": "+34600111222",
    "document": "12345678Z",
    "privacy_accepted": true
  },
  "locale": "ca",
  "notify": true
}
Petición de ejemplo
curl -X POST "https://api.kalmee.com/v1/appointments" \
  -H "Authorization: Bearer $KALMEE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"service_id":"9f00a1b2-c3d4-4e5f-8a6b-7c8d9e0f1a2b","starts_at":"2026-10-22T07:20:00Z","citizen":{"name":"Marta Soler","email":"marta@example.com","phone":"+34600111222","document":"12345678Z","privacy_accepted":true},"locale":"ca","notify":true}'
Respuesta de ejemplo · 201
{
  "data": {
    "id": "e2d4f6a8-b0c2-4d4e-8f6a-8b0c2d4e6f8a",
    "office_id": "c41d8e2a-6b7f-4a1c-9d3e-5f6a7b8c9d0e",
    "service_id": "9f00a1b2-c3d4-4e5f-8a6b-7c8d9e0f1a2b",
    "starts_at": "2026-10-22T07:20:00Z",
    "ends_at": "2026-10-22T07:30:00Z",
    "status": "booked",
    "source": "online",
    "ticket_id": null,
    "created_at": "2026-10-19T17:40:11Z",
    "cancelled_at": null,
    "checked_in_at": null,
    "code": "48213307",
    "confirmation_email_sent": true
  }
}

Errores de esta operación: validation_failed, idempotency_key_required, idempotency_key_reused, request_in_progress, not_found, service_unavailable, slot_unavailable, slot_full, duplicate_booking

post/appointments/{appointment_id}/cancel· Anular una cita

Anula una cita reservada. Con `notify` (por defecto, true) se avisa a la persona por correo. El motivo (`reason`) queda en el registro de actividad.

Alcance: queue:write

Parámetros
  • appointment_idruta · ObligatorioIdentificador de la cita.
Cuerpo de la petición
{
  "notify": true,
  "reason": "La persona ha llamado para anularla"
}
Petición de ejemplo
curl -X POST "https://api.kalmee.com/v1/appointments/e2d4f6a8-b0c2-4d4e-8f6a-8b0c2d4e6f8a/cancel" \
  -H "Authorization: Bearer $KALMEE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"notify":true,"reason":"La persona ha llamado para anularla"}'
Respuesta de ejemplo · 200
{
  "data": {
    "id": "e2d4f6a8-b0c2-4d4e-8f6a-8b0c2d4e6f8a",
    "office_id": "c41d8e2a-6b7f-4a1c-9d3e-5f6a7b8c9d0e",
    "service_id": "9f00a1b2-c3d4-4e5f-8a6b-7c8d9e0f1a2b",
    "starts_at": "2026-10-22T07:20:00Z",
    "ends_at": "2026-10-22T07:30:00Z",
    "status": "cancelled",
    "source": "online",
    "ticket_id": null,
    "created_at": "2026-10-19T17:40:11Z",
    "cancelled_at": "2026-10-20T09:02:00Z",
    "checked_in_at": null,
    "cancellation_email_sent": true
  }
}

Errores de esta operación: validation_failed, idempotency_key_required, idempotency_key_reused, request_in_progress, not_found, invalid_state

Estadísticas

get/stats· Estadísticas

Los mismos números que la pantalla de estadísticas: totales, por día, por hora, por trámite y por mesa. Sin el desglose por persona. Como mucho 366 días.

Alcance: read

Parámetros
  • fromconsulta · ObligatorioPrimer día (AAAA-MM-DD), en la zona horaria de la oficina.
  • toconsulta · ObligatorioÚltimo día, incluido. Como mucho 366 días desde «from».
  • office_idconsulta · OpcionalSolo esta oficina.
Petición de ejemplo
curl "https://api.kalmee.com/v1/stats?from=2026-10-01&to=2026-10-19&office_id=c41d8e2a-6b7f-4a1c-9d3e-5f6a7b8c9d0e" \
  -H "Authorization: Bearer $KALMEE_API_KEY"
Respuesta de ejemplo · 200
{
  "data": {
    "from": "2026-10-01",
    "to": "2026-10-19",
    "office_id": "c41d8e2a-6b7f-4a1c-9d3e-5f6a7b8c9d0e",
    "totals": {
      "issued": 1840,
      "done": 1702,
      "no_show": 61,
      "cancelled": 12,
      "appointments": 420,
      "transfers": 35,
      "avg_wait_s": 512,
      "p90_wait_s": 1260,
      "avg_service_s": 388
    },
    "daily": [
      {
        "date": "2026-10-19",
        "issued": 112,
        "done": 104,
        "no_show": 3,
        "avg_wait_s": 498
      }
    ],
    "hourly": [
      {
        "hour": 9,
        "issued": 41
      }
    ],
    "by_service": [
      {
        "service_id": "9f00a1b2-c3d4-4e5f-8a6b-7c8d9e0f1a2b",
        "code_prefix": "A",
        "name_i18n": {
          "es": "Padrón",
          "ca": "Padró",
          "cat": "Padró",
          "gl": "Padrón",
          "eu": "Errolda"
        },
        "issued": 920,
        "done": 870,
        "no_show": 20,
        "avg_wait_s": 530,
        "avg_service_s": 402
      }
    ],
    "by_desk": [
      {
        "desk_id": "55aa6b7c-8d9e-4f0a-b1c2-d3e4f5a6b7c8",
        "number": 4,
        "name": "Mesa 4",
        "office_id": "c41d8e2a-6b7f-4a1c-9d3e-5f6a7b8c9d0e",
        "done": 410,
        "no_show": 9,
        "avg_wait_s": 505,
        "avg_service_s": 371
      }
    ]
  }
}

Errores de esta operación: validation_failed, not_found

Servidor MCP (asistentes de IA)

Con el servidor MCP, un asistente de IA (Claude, ChatGPT u otro compatible con MCP) puede consultar y usar Kalmee para tu organización: ver la cola en directo, las citas y las estadísticas, dar números y reservar o anular citas.

Dirección del servidor (Streamable HTTP):

https://app.kalmee.com/api/mcp

Usa las mismas claves de API, con los mismos alcances, límites de peticiones y registro de actividad. Crea una clave solo para el asistente, con el alcance mínimo: read para consultar y queue:write solo si también debe dar números o gestionar citas.

Authorization: Bearer kal_live_a1b2c3d4_…

Está incluido en los planes con API: Profesional y Multisede.

Claude

En claude.ai o Claude Desktop: Ajustes → Conectores → Añadir conector personalizado. Pon la dirección del servidor y, en Cabeceras de la petición, Authorization con el valor «Bearer» seguido de tu clave. Esta opción la ve quien administra la organización en Claude y se está activando por fases.

Si tu Claude Desktop no tiene esa opción, añade esto a claude_desktop_config.json (usa mcp-remote, que necesita Node.js):

{
  "mcpServers": {
    "kalmee": {
      "command": "npx",
      "args": [
        "-y",
        "mcp-remote",
        "https://app.kalmee.com/api/mcp",
        "--header",
        "Authorization:${KALMEE_AUTH}"
      ],
      "env": {
        "KALMEE_AUTH": "Bearer kal_live_…"
      }
    }
  }
}

En Claude Code, desde la terminal:

claude mcp add --transport http kalmee https://app.kalmee.com/api/mcp \
  --header "Authorization: Bearer $KALMEE_API_KEY"

ChatGPT

Activa el modo desarrollador (Ajustes → Aplicaciones → Opciones avanzadas) y crea un conector con la dirección del servidor. Si el formulario te deja poner una clave o un token, pon tu clave de Kalmee. Si solo ofrece OAuth, todavía no se puede conectar: el acceso con OAuth está en preparación.

Herramientas

  • Herramienta: get_organizationAlcance: readSolo lecturaQué hace: Devuelve la organización de la clave (nombre, zona horaria, idioma), los alcances de la clave y su límite de peticiones.
  • Herramienta: list_officesAlcance: readSolo lecturaQué hace: Lista las oficinas de la organización, con su código DIR3 (filtro dir3). Empieza por aquí: casi todas las demás herramientas piden un office_id.
  • Herramienta: get_officeAlcance: readSolo lecturaQué hace: Datos de una oficina y si está abierta ahora (horario, festivos y jornadas especiales).
  • Herramienta: list_servicesAlcance: readSolo lecturaQué hace: Trámites (servicios) de una oficina, con su prefijo de ticket, código SIA (filtro sia), duración, límite diario y si dan cita por internet.
  • Herramienta: list_desksAlcance: readSolo lecturaQué hace: Mesas activas de una oficina: si están abiertas, en pausa o cerradas, y el ticket que atienden ahora.
  • Herramienta: queue_statusAlcance: readSolo lecturaQué hace: Cola en directo de una oficina: personas esperando, llamadas y en atención por trámite, mesas abiertas y espera estimada (minutos).
  • Herramienta: check_availabilityAlcance: readSolo lecturaQué hace: Huecos libres para pedir cita en un trámite entre dos fechas (como mucho 31 días), por día de la oficina.
  • Herramienta: list_appointmentsAlcance: readSolo lecturaQué hace: Citas de la organización entre dos fechas (como mucho 92 días), opcionalmente de una oficina o un estado. Sin datos personales: solo horas, estados y trámites. Paginado con cursor.
  • Herramienta: today_statsAlcance: readSolo lecturaQué hace: Estadísticas de hoy (tickets, esperas, atención, por trámite y por mesa) de toda la organización o de una oficina. Sin desglose por persona.
  • Herramienta: stats_reportAlcance: readSolo lecturaQué hace: Estadísticas entre dos fechas (como mucho 366 días), de toda la organización o de una oficina: totales, por día, por hora, por trámite y por mesa.
  • Herramienta: issue_ticketAlcance: queue:writeModifica datosQué hace: Da un número sin cita en un trámite, como el quiosco (misma numeración, límite diario y horario). Devuelve el código, las personas delante, la espera estimada y el enlace de seguimiento. Repetir con el mismo client_request_id no da otro número.
  • Herramienta: book_appointmentAlcance: queue:writeModifica datosQué hace: Reserva una cita para una persona en un hueco libre (míralo antes con check_availability), con las mismas comprobaciones que la página de cita previa. Necesita el nombre y el correo de la persona y su consentimiento (privacy_accepted). Le envía el correo de confirmación salvo notify: false.
  • Herramienta: cancel_appointmentAlcance: queue:writeNo se puede deshacerQué hace: Anula una cita reservada y avisa a la persona por correo salvo notify: false. No se puede deshacer.

Las herramientas que modifican datos piden un client_request_id: si el asistente repite la llamada con el mismo, no se da otro número ni se reserva dos veces.

El asistente ve los mismos datos que la API: nunca el nombre, el correo, el teléfono ni el DNI de las personas. Revisa lo que propone antes de que reserve o anule citas.

Si la clave se ha compartido por error, revócala en Integraciones (API): deja de funcionar al momento.

Webhooks

Con los webhooks, Kalmee avisa a tu programa al momento: envía un POST con JSON a la dirección que configures en Kalmee → Integraciones (API) cada vez que pasa algo en la cola o en las citas.

Añade una dirección https pública y elige los avisos. Kalmee te enseña una sola vez el secreto de firma, que empieza por whsec_. Con «Enviar prueba» recibes un evento ping.

Eventos

  • Evento: ticket.issuedCuándo: Se da un turno
  • Evento: ticket.calledCuándo: Se llama un turno o se vuelve a llamar
  • Evento: ticket.servedCuándo: Se termina de atender
  • Evento: ticket.no_showCuándo: La persona no se presenta
  • Evento: ticket.cancelledCuándo: Se anula o caduca al cerrar el día
  • Evento: ticket.transferredCuándo: Se pasa a otro trámite o a otra mesa
  • Evento: appointment.createdCuándo: Se reserva una cita
  • Evento: appointment.cancelledCuándo: Se anula una cita
  • Evento: appointment.rescheduledCuándo: Cambia la hora de una cita
  • Evento: appointment.checked_inCuándo: La persona llega y confirma la cita
  • Evento: pingCuándo: Pulsas «Enviar prueba» en Integraciones (API).

Contenido

Todos los eventos tienen la misma forma: id (único: sirve para descartar repetidos), type, api_version, occurred_at, org_id y data. No llevan nombre, correo, teléfono ni DNI: solo identificadores, el número de turno, la mesa, horas y estados.

Ejemplos:

{
  "id": "evt_01f3a6c29b7e4d1a8c5f2e7b9d4a6f10",
  "type": "ticket.called",
  "api_version": "2026-10",
  "occurred_at": "2026-10-20T06:14:03.000Z",
  "org_id": "7a0c5f3e-2b1d-4c8e-9f6a-1d2e3f4a5b6c",
  "data": {
    "ticket": {
      "id": "1b9e2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d",
      "number_label": "A023",
      "status": "called",
      "kind": "walkin",
      "source": "in_person",
      "office_id": "c41d8e2a-6b7f-4a1c-9d3e-5f6a7b8c9d0e",
      "service_id": "9f00a1b2-c3d4-4e5f-8a6b-7c8d9e0f1a2b",
      "ticket_type_id": null,
      "appointment_id": null,
      "desk_id": "55aa6b7c-8d9e-4f0a-b1c2-d3e4f5a6b7c8",
      "desk_number": 4,
      "call_count": 1,
      "transfer_count": 0,
      "issued_at": "2026-10-20T06:02:41.000Z",
      "called_at": "2026-10-20T06:14:03.000Z",
      "last_called_at": "2026-10-20T06:14:03.000Z",
      "started_at": null,
      "finished_at": null
    },
    "recall": false
  }
}
{
  "id": "evt_01f3a6c29b7e4d1a8c5f2e7b9d4a6f10",
  "type": "appointment.created",
  "api_version": "2026-10",
  "occurred_at": "2026-10-20T06:14:03.000Z",
  "org_id": "7a0c5f3e-2b1d-4c8e-9f6a-1d2e3f4a5b6c",
  "data": {
    "appointment": {
      "id": "e2d4f6a8-b0c2-4d4e-8f6a-8b0c2d4e6f8a",
      "status": "booked",
      "source": "online",
      "office_id": "c41d8e2a-6b7f-4a1c-9d3e-5f6a7b8c9d0e",
      "service_id": "9f00a1b2-c3d4-4e5f-8a6b-7c8d9e0f1a2b",
      "starts_at": "2026-10-22T07:20:00.000Z",
      "ends_at": "2026-10-22T07:30:00.000Z",
      "ticket_id": null,
      "created_at": "2026-10-18T17:40:11.000Z",
      "cancelled_at": null,
      "checked_in_at": null
    }
  }
}

Cabeceras

POST /tu/direccion HTTP/1.1
Content-Type: application/json
User-Agent: Kalmee-Webhooks/1
Kalmee-Signature: t=1792476843,v1=5f2c8a…e9
Kalmee-Event-Id: evt_01f3a6c29b7e4d1a8c5f2e7b9d4a6f10
Kalmee-Event-Type: ticket.called
Kalmee-Delivery-Id: dlv_8d2e4f6a0b1c4d3e9f5a7b8c6d4e2f10
  • Kalmee-Signature: t (segundos Unix) y v1, la firma HMAC-SHA256.
  • Kalmee-Event-Id: el mismo id del cuerpo.
  • Kalmee-Event-Type: el mismo type del cuerpo.
  • Kalmee-Delivery-Id: igual en todos los reintentos de un mismo envío.

Comprobar la firma

Calcula HMAC-SHA256 de «t.cuerpo» con el secreto entero (whsec_…) como clave y compáralo con v1 en tiempo constante. Usa el cuerpo tal como llega, sin volver a convertirlo a JSON, y rechaza las firmas de más de 5 minutos.

Node.js

// Node.js 18+ (Express): usa el cuerpo sin procesar, tal como llega.
import crypto from "node:crypto";
import express from "express";

const SECRET = process.env.KALMEE_WEBHOOK_SECRET; // whsec_…
const app = express();

app.post("/kalmee/webhooks", express.raw({ type: "application/json" }), (req, res) => {
  const header = req.get("Kalmee-Signature") ?? "";
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  const t = Number(parts.t);
  const signatures = header.split(",").filter((p) => p.startsWith("v1=")).map((p) => p.slice(3));
  if (!t || Math.abs(Date.now() / 1000 - t) > 300) return res.sendStatus(400);

  const expected = crypto.createHmac("sha256", SECRET).update(`${t}.${req.body}`).digest("hex");
  const ok = signatures.some((s) => s.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(s), Buffer.from(expected)));
  if (!ok) return res.sendStatus(400);

  const event = JSON.parse(req.body.toString("utf8"));
  res.sendStatus(204); // responde ya y procesa después
  // descarta repetidos por event.id y trata event.type
});

PHP

<?php
// PHP 7.4+: usa el cuerpo sin procesar, tal como llega.
$secret = getenv('KALMEE_WEBHOOK_SECRET'); // whsec_…
$body = file_get_contents('php://input');
$header = $_SERVER['HTTP_KALMEE_SIGNATURE'] ?? '';

$t = 0;
$signatures = [];
foreach (explode(',', $header) as $part) {
    [$key, $value] = array_pad(explode('=', trim($part), 2), 2, '');
    if ($key === 't') { $t = (int) $value; }
    if ($key === 'v1') { $signatures[] = $value; }
}
if ($t === 0 || abs(time() - $t) > 300) { http_response_code(400); exit; }

$expected = hash_hmac('sha256', $t . '.' . $body, $secret);
$ok = false;
foreach ($signatures as $signature) {
    if (hash_equals($expected, $signature)) { $ok = true; }
}
if (!$ok) { http_response_code(400); exit; }

$event = json_decode($body, true);
http_response_code(204); // responde ya y procesa después
// descarta repetidos por $event['id'] y trata $event['type']

Respuesta, reintentos y orden

Responde con un 2xx en menos de 10 segundos y haz el trabajo después. Si no, Kalmee reintenta al cabo de 1 min, 5 min, 30 min, 2 h, 6 h y 15 h (unas 24 horas en total, con un margen aleatorio). Las redirecciones no se siguen y un 410 Gone desactiva la dirección.

1 min → 5 min → 30 min → 2 h → 6 h → 15 h

El orden no está garantizado y un evento puede llegar más de una vez: descarta los repetidos por id y quédate con el occurred_at más reciente.

Si una dirección falla más de 15 veces seguidas durante al menos un día, Kalmee la desactiva y lo avisa en Integraciones (API).

Solo se envía a direcciones https públicas (puertos 443 u 8443), nunca a redes privadas o locales. El cuerpo de la respuesta no se lee ni se guarda.

Próximamente

    ¿Necesitas ayuda con una integración? Escribe a soporte@kalmee.com.