Appearance
Ligas de agenda
Una liga de agenda (BookingLink) es una URL pública estilo Calendly que vive en book.fi-nova.com/<booking_slug>/<link_slug> y permite a terceros agendar una llamada con un usuario de tu workspace. Cada liga configura su propio horario disponible, duración, buffers, ubicación y campos del formulario.
URL pública = booking_slug + link_slug
Cada usuario del workspace tiene su propio booking_slug (configurable en Configuración → Agendas). El slug de la liga es único por usuario, no globalmente: dos usuarios distintos pueden tener cada uno una liga /intro. La URL completa toma la forma book.fi-nova.com/<booking_slug>/<link_slug> y public_url en la respuesta ya viene armada. Si el usuario no ha configurado su booking_slug, public_url regresa null — la liga existe pero aún no es accesible públicamente.
El objeto Booking Link
json
{
"id": "65b1c2d3e4f5a6b7c8d9e012",
"user_id": "64f1a2b3c4d5e6f701234567",
"slug": "demo-30min",
"title": "Demo del producto (30 min)",
"description": "Te muestro Finova en vivo.",
"color": "#2563eb",
"kind": "meeting",
"duration_minutes": 30,
"buffer_before_minutes": 0,
"buffer_after_minutes": 10,
"min_notice_minutes": 120,
"max_advance_days": 60,
"slot_interval_minutes": 30,
"timezone": "America/Mexico_City",
"availability": {
"1": [{ "from": "09:00", "to": "13:00" }, { "from": "15:00", "to": "18:00" }],
"2": [{ "from": "09:00", "to": "18:00" }],
"3": [{ "from": "09:00", "to": "18:00" }],
"4": [{ "from": "09:00", "to": "18:00" }],
"5": [{ "from": "09:00", "to": "13:00" }]
},
"blocked_dates": ["2026-12-25"],
"custom_fields": [
{ "key": "empresa", "label": "Empresa", "field_type": "text", "required": true },
{ "key": "interes", "label": "¿Qué te interesa?", "field_type": "textarea", "required": false },
{ "key": "newsletter", "label": "Suscribirme al newsletter", "field_type": "checkbox", "required": false }
],
"active": true,
"create_lead": true,
"lead_source": "web",
"location_type": "online",
"location_value": null,
"auto_meet": true,
"confirmation_message": "¡Listo! Te llegará un correo con los detalles.",
"public_url": "https://book.fi-nova.com/fabian-garcia/demo-30min",
"created_at": "2026-05-15T10:00:00Z",
"updated_at": "2026-05-15T10:00:00Z"
}Campos
| Campo | Tipo | Descripción |
|---|---|---|
user_id | string | Host de la liga. Inmutable después de creada. |
slug | string | Identificador en la URL pública. [a-z0-9-]{3,64}. Único por usuario (no globalmente). |
public_url | string | null | Read-only. URL completa armada con el booking_slug del host + el slug de la liga. null cuando el host aún no ha configurado su booking_slug. |
title | string | Título visible en la landing. Requerido. |
description | string | Markdown plano (sin renderizar). |
kind | enum | meeting o event. |
duration_minutes | int | Duración de cada slot. |
buffer_before_minutes / buffer_after_minutes | int | Tiempo extra antes/después de cada cita. |
min_notice_minutes | int | Mínimo de anticipación para agendar. |
max_advance_days | int | Máximo de días en el futuro permitidos. |
slot_interval_minutes | int | Granularidad de los slots ofrecidos (ej. 15 → 9:00, 9:15, …). |
timezone | string | IANA tz. La availability se interpreta en esta tz. |
availability | object | Hash con claves "0"–"6" (Domingo–Sábado), cada valor es un array de { from, to } en formato HH:MM. |
blocked_dates | array | Fechas YYYY-MM-DD que invalidan la availability semanal. |
custom_fields | array | Campos adicionales del formulario público. Cada uno: { key, label, field_type, required }. Tipos: text, textarea, email, checkbox. |
active | bool | Cuando false, la liga responde 404 en book.fi-nova.com. |
create_lead | bool | Si true, cada submission crea un Lead en Prospección. |
lead_source | string | Valor que se asigna al source del lead generado. |
location_type | enum | online, phone, in_person, none. |
location_value | string | Detalle (dirección, número, etc.). |
auto_meet | bool | Solo para location_type=online: genera Meet link si el host tiene Google conectado. |
confirmation_message | string | Mensaje que se muestra al booker después de agendar. |
public_url | string | Read-only. URL completa de la landing. |
Listar ligas
GET
/api/v1/calendar/booking-linksread-booking_linksQuery params: limit, starting_after, ending_before, include_total, page (ver Paginación).
Filtros adicionales: user_id, active (true/false).
Obtener una liga
GET
/api/v1/calendar/booking-links/:idread-booking_linksEl segmento :id acepta el id (ObjectId) o el slug de la liga. Ver Identificar un recurso.
Crear una liga
POST
/api/v1/calendar/booking-linkscreate-booking_linksCampos aceptados en data: user_id (requerido), slug (requerido), title (requerido), description, color, kind, duration_minutes, buffer_before_minutes, buffer_after_minutes, min_notice_minutes, max_advance_days, slot_interval_minutes, timezone, availability, blocked_dates, custom_fields, active, create_lead, lead_source, location_type, location_value, auto_meet, confirmation_message.
bash
curl -X POST https://developers.fi-nova.com/api/v1/calendar/booking-links \
-H 'Authorization: Bearer finova_sk_TU_SECRETO' \
-H 'Content-Type: application/json' \
-d '{
"data": {
"user_id": "64f1a2b3c4d5e6f701234567",
"slug": "demo-30min",
"title": "Demo del producto (30 min)",
"duration_minutes": 30,
"kind": "meeting",
"location_type": "online",
"auto_meet": true,
"availability": {
"1": [{ "from": "09:00", "to": "18:00" }],
"2": [{ "from": "09:00", "to": "18:00" }],
"3": [{ "from": "09:00", "to": "18:00" }],
"4": [{ "from": "09:00", "to": "18:00" }],
"5": [{ "from": "09:00", "to": "13:00" }]
},
"custom_fields": [
{ "key": "empresa", "label": "Empresa", "field_type": "text", "required": true }
]
}
}'Actualizar una liga
PATCH
/api/v1/calendar/booking-links/:idupdate-booking_linksMismos campos que create, excepto user_id (inmutable post-creación: cambiar el host rompería las reservas históricas).
Consultar disponibilidad
GET
/api/v1/calendar/booking-links/:id/availabilityread-booking_linksDevuelve los slots libres que la landing pública mostraría, considerando:
- La availability semanal de la liga.
min_notice_minutes/max_advance_days.buffer_before_minutes/buffer_after_minutes.- Eventos existentes del host en Finova (siempre).
- Eventos del host en Google Calendar (si está conectado).
- El setting de usuario
block_overlapping_bookings(si está apagado, solo se evitan colisiones contra otras reservas, no contra eventos sueltos).
Query params:
| Param | Default | Notas |
|---|---|---|
from | hoy | YYYY-MM-DD. |
to | from + 14d | YYYY-MM-DD. Capado a from + 60d por performance. |
Respuesta:
json
{
"data": {
"2026-05-20": ["09:00", "09:30", "10:00", "10:30"],
"2026-05-21": ["09:00", "11:00", "11:30"]
},
"meta": {
"timezone": "America/Mexico_City",
"from": "2026-05-20",
"to": "2026-05-26"
}
}Los días sin slots disponibles se omiten del hash.
Performance
La availability service hace una sola query Mongo por colección involucrada (calendar_bookings, calendar_events) + una llamada a Google FreeBusy si el host está conectado. La generación de slots es in-memory y O(días × ventanas × slots). El cap de 60 días garantiza latencia acotada incluso para slot_interval_minutes de 5.
Eliminar una liga
DELETE
/api/v1/calendar/booking-links/:iddelete-booking_linksSoft-delete (paranoia): la liga deja de aparecer en book.fi-nova.com pero las reservas previas conservan su referencia. 204 No Content en éxito.
Errores específicos
error | Cuándo |
|---|---|
invalid_user (422) | user_id ausente o no pertenece al workspace. |
validation_failed (422) | slug ya existe para ese mismo usuario, formato de slug inválido, campos requeridos ausentes, custom_fields malformados. |
invalid_date (400) | from o to no son fechas ISO. |