REST + JSON, webhooks firmados y servidor MCP. Incluida en todos los planes.
kal_live_…).start devueltos.curl https://staging.164-132-79-6.sslip.io/api/v1/services -H "Authorization: Bearer kal_live_XXXX"
curl "https://staging.164-132-79-6.sslip.io/api/v1/availability?service_id=svc_…&from=2026-11-02&to=2026-11-08" \
-H "Authorization: Bearer kal_live_XXXX"
curl -X POST https://staging.164-132-79-6.sslip.io/api/v1/bookings \
-H "Authorization: Bearer kal_live_XXXX" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 6f1c…" \
-d '{"service_id":"svc_…","start":"2026-11-03T13:00:00.000Z",
"customer":{"name":"Ana Pérez","email":"ana@ejemplo.com","phone":"+56912345678"}}'https://staging.164-132-79-6.sslip.io/api/v1 · versión 2026-10-21 (cabecera KalendarIA-Version).Authorization: Bearer kal_live_…. Claves de lectura o de lectura+escritura.svc_, stf_, res_, bkg_, cus_, evt_, whe_.25000 = $25.000), EUR en céntimos (2900 = 29,00 €).Idempotency-Key en cualquier POST (24 h). La misma clave con otro cuerpo devuelve 409.limit (≤ 100) y cursor = next_cursor de la respuesta anterior.RateLimit-*).code estable: slot_unavailable, hold_expired, validation_error, invalid_state, cancel_window_passed, rate_limited…409 slot_unavailable./accountDatos del negocio/servicesListar servicios/servicesCrear servicio/services/{id}Ver servicio/services/{id}Actualizar servicio/services/{id}Eliminar servicio (si tiene reservas se archiva)/staffListar profesionales (con horario semanal)/resourcesListar recursos (boxes, sillones, salas, equipos)/availabilityHoras libres de un servicio/holdsRetener una hora (10 minutos)/bookingsListar reservas/bookingsCrear reserva/bookings/{id}Ver reserva/bookings/{id}/cancelCancelar/bookings/{id}/rescheduleCambiar hora/bookings/{id}/confirmAprobar (pending_approval → confirmed)/bookings/{id}/completeMarcar como atendida/bookings/{id}/no_showMarcar como no asistió/customersListar o buscar clientes/customersCrear o actualizar cliente (por email/teléfono)/customers/importAlta masiva (hasta 1.000): deduplica por email/teléfono y solo completa datos vacíos de los que ya existen/customers/{id}Ver cliente/customers/{id}Actualizar cliente/customers/{id}Suprimir datos personales (RGPD / Ley 21.719)/discount_codesListar códigos de descuento (con usos)/discount_codesCrear código: kind percent (1–100) o amount (unidades mínimas), service_ids, valid_from/until, max_uses, max_per_customer, first_visit_only/reviewsReseñas de clientes (status: published, pending, hidden)/gift_cardsListar tarjetas regalo/gift_cardsCrear (vender) una tarjeta regalo/gift_cards/{code}Consultar saldo por código (ABCD-EFGH) o id/gift_cards/{code}/redeemCanjear saldo/webhook_endpointsListar webhooks/webhook_endpointsSuscribir un webhook (REST hook)/webhook_endpoints/{id}Eliminar webhook/webhook_endpoints/{id}/deliveriesÚltimas entregas de un webhook/eventsRegistro de eventos (reconciliación)/events/{id}Ver eventoEsquemas completos en openapi.json (impórtalo en Postman, Insomnia o un generador de SDK).
Retén la hora 10 minutos mientras el cliente termina y confírmala después: nadie más puede tomarla mientras tanto.
POST /api/v1/holds {"service_id":"svc_…","start":"2026-11-03T13:00:00.000Z"}
→ {"id":"bkg_…","object":"hold","status":"held","hold_expires_at":"…"}
POST /api/v1/bookings {"hold_id":"bkg_…","customer":{"name":"Ana","phone":"+34612345678"}}
→ {"id":"bkg_…","status":"confirmed","manage_url":"…"}Formato Standard Webhooks: cabeceras webhook-id, webhook-timestamp y webhook-signature (v1,<base64>). Firma HMAC-SHA256 de id.timestamp.cuerpo con el secreto (whsec_…, se decodifica en base64). Reintentos durante ~42 h; responde 2xx en menos de 10 s.
// Node.js
import crypto from "node:crypto";
function verify(secret, headers, rawBody) {
const key = Buffer.from(secret.replace(/^whsec_/, ""), "base64");
const msg = `${headers["webhook-id"]}.${headers["webhook-timestamp"]}.${rawBody}`;
const sig = crypto.createHmac("sha256", key).update(msg).digest("base64");
const fresh = Math.abs(Date.now() / 1000 - Number(headers["webhook-timestamp"])) < 300;
return fresh && headers["webhook-signature"].split(" ").includes(`v1,${sig}`);
}Eventos:
booking.heldbooking.createdbooking.confirmedbooking.pending_approvalbooking.pending_paymentbooking.rescheduledbooking.canceledbooking.completedbooking.no_showbooking.checked_inbooking.expiredpayment.succeededpayment.failedpayment.refundedgift_card.purchasedreview.createdcustomer.createdcustomer.updatedwaitlist.joinedwaitlist.notifiedSi se pierde alguno, GET /api/v1/events devuelve el registro completo para reconciliar.
Streamable HTTP en https://staging.164-132-79-6.sslip.io/api/mcp. Herramientas: datos del negocio, servicios, horas libres, crear, mover, cancelar y aprobar reservas, marcar asistencia, buscar clientes y resumen del día.
Claude y ChatGPT (OAuth 2.1, sin claves): añade un conector personalizado con la URL https://staging.164-132-79-6.sslip.io/api/mcp. El cliente descubre el servidor de autorización en /.well-known/oauth-protected-resource/api/mcp, se registra solo (RFC 7591) y te pide entrar en KalendarIA y elegir el negocio. Las apps conectadas se ven y se desconectan en Panel → Desarrolladores.
Con clave de API (Claude Code, scripts, n8n):
claude mcp add --transport http kalendaria https://staging.164-132-79-6.sslip.io/api/mcp \ --header "Authorization: Bearer kal_live_XXXX"
Un archivo sin dependencias (Node 18+, Deno, Bun, navegador) con tipos: kalendaria.mjs · kalendaria.d.ts. Pone la Idempotency-Key en cada escritura, reintenta 429/5xx con la misma clave y lanza KalendariaError con el code estable.
import { Kalendaria, verifyWebhook } from "https://staging.164-132-79-6.sslip.io/sdk/kalendaria.mjs";
const kal = new Kalendaria({ apiKey: process.env.KALENDARIA_KEY });
const { days } = await kal.availability({ service_id: "svc_…", from: "2026-11-03" });
const booking = await kal.bookings.create({
service_id: "svc_…", start: days[0].slots[0].start,
customer: { name: "Ana Pérez", email: "ana@example.com" },
});
for await (const b of kal.paginate((q) => kal.bookings.list(q))) console.log(b.id);
// En tu endpoint de webhooks (cuerpo sin parsear):
const event = await verifyWebhook(process.env.WHSEC, req.headers, rawBody);Cada negocio tiene un servidor MCP sin login en https://staging.164-132-79-6.sslip.io/api/mcp/public/<slug> con cuatro herramientas: get_business, list_services, get_availability (máximo 14 días) y create_booking (exige nombre, email, teléfono y privacy_consent: true; devuelve el enlace de gestión y, si hay abono, el de pago). Mismos límites anti-abuso que la página pública; no expone clientes ni datos internos.
KalendarIA usa el patrón «REST Hooks»: la herramienta se suscribe a un evento creando un webhook y lo borra al desactivar el escenario. Autenticación: cabecera Authorization: Bearer kal_live_… (o kal_test_… para probar sin tocar tu agenda real).
POST /api/v1/webhook_endpoints con {"url": "<url del webhook>", "events": ["booking.created"]}. Guarda el id de la respuesta.DELETE /api/v1/webhook_endpoints/{id}.GET /api/v1/bookings?limit=3.POST /api/v1/bookings), buscar o crear cliente (GET/POST /api/v1/customers), horas libres (GET /api/v1/availability), cancelar (POST /api/v1/bookings/{id}/cancel), tarjeta regalo (POST /api/v1/gift_cards).GET /api/v1/events devuelve los eventos en orden; guarda el último id visto.En n8n: nodo «Webhook» como disparador + nodo «HTTP Request» para suscribirlo una vez; o «HTTP Request» con credencial «Header Auth». En Make: módulo «Webhooks → Custom webhook» y registra su URL con la llamada de arriba. En Zapier: «Webhooks by Zapier → Catch Hook» y regístrala igual (la app oficial de Zapier está en camino).
<div data-kalendaria="tu-negocio"></div> <script src="https://staging.164-132-79-6.sslip.io/embed.js" async></script> <!-- o un botón flotante --> <script src="https://staging.164-132-79-6.sslip.io/embed.js" data-kalendaria-button="tu-negocio" data-label="Reservar" async></script>
Pasa los UTM, fbclid, gclid y las cookies _fbp/_fbc/_ga a la reserva y, al reservar, lanza dataLayer.push({event: "booking_created"}) y el evento Schedule del píxel de Meta con eventID = id de la reserva.