VitrinaAPI

Conectar tu IA a Vitrina y dejar que actúe

Autoriza los permisos de escritura correctos y deja que actúe de verdad.

Tu asistente ya puede leer el workspace: leads, conversaciones, la agenda. Esta receta lo deja además actuar, solo en las áreas que tú autorizas. Al final vas a tener una conexión que reserva una cita de verdad. Y vas a saber leer, con precisión, qué le diste permiso de hacer.

Trampa

Un permiso de escritura no es una casilla genérica: es una frase, y hay que leerla entera

Marcar «Agenda» en la pantalla de autorización no habilita todo lo que suena a agenda. Ese paquete crea y mueve citas, y lo dice así, con todas sus letras: no incluye cancelarlas. Cancelar pide dos permisos que «Agenda» nunca entrega, porque cancelar además le avisa al cliente. Antes de confiar en un paquete, lee su frase completa, no solo el nombre.

Antes de empezar

  • Una cuenta del workspace con el rol que alcanza las áreas que vas a autorizar. Un rol sin Leads no puede otorgar el permiso de Leads, aunque lo intentes.
  • El cliente de IA ya instalado: Claude, Claude Code o Cursor. Ahí está el paso a paso para autorizar.
  • Nada de credenciales para copiar a mano. La conexión OAuth guarda el secreto por ti.

1. Conéctate desde tu cliente

El camino es el mismo para cualquier workspace: pegas https://api.vitrinadev.com/mcp, entras a Vitrina y autorizas. La pantalla de permisos trae, debajo del acceso de lectura, un interruptor por cada área que puede escribir. Todos nacen apagados. Si no tocas ninguno, la conexión no cambia nada.

2. Mira qué alcanza tu conexión

Con «Agenda» autorizado, pregúntale a tu cliente qué puede hacer con la agenda, o llama la herramienta genérica directo:

curl -X POST https://api.vitrinadev.com/mcp \
  -H "Authorization: Bearer $VITRINA_CONNECTOR_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"search_operations","arguments":{"query":"appointment"}}}'
{
  "total": 10,
  "returned": 10,
  "tags": ["Analytics", "Appointments", "Contacts", "Conversations", "Help Centers", "Leads", "Locations", "Pipelines", "Price Approvals", "Public Stock", "Teams", "Tickets", "Vehicles", "WhatsApp Flows"],
  "operations": [
    { "operation_id": "appointment_get", "method": "GET", "path": "/appointments/{id}", "tag": "Appointments", "tier": "beta", "destructive": false, "writes": false, "summary": "Fetch one appointment", "scopes": ["appointments:read"] },
    { "operation_id": "appointment_type_get", "method": "GET", "path": "/appointment-types/{id}", "tag": "Appointments", "tier": "beta", "destructive": false, "writes": false, "summary": "Get an appointment type", "scopes": ["appointment_types:read"] },
    { "operation_id": "appointment_types_list", "method": "GET", "path": "/appointment-types", "tag": "Appointments", "tier": "beta", "destructive": false, "writes": false, "summary": "List appointment types", "scopes": ["appointment_types:read"] },
    { "operation_id": "appointment_update", "method": "PATCH", "path": "/appointments/{id}", "tag": "Appointments", "tier": "beta", "destructive": false, "writes": true, "summary": "Reschedule, reassign or close an appointment", "scopes": ["appointments:write"] },
    { "operation_id": "appointments_availability_list", "method": "GET", "path": "/appointments/availability", "tag": "Appointments", "tier": "beta", "destructive": false, "writes": false, "summary": "Open slots", "scopes": ["appointments:read"] },
    { "operation_id": "appointments_calendar_list", "method": "GET", "path": "/appointments/calendar", "tag": "Appointments", "tier": "beta", "destructive": false, "writes": false, "summary": "Appointments in a calendar window", "scopes": ["appointments:read"] },
    { "operation_id": "appointments_config_list", "method": "GET", "path": "/appointments/config", "tag": "Appointments", "tier": "beta", "destructive": false, "writes": false, "summary": "The scheduling configuration", "scopes": ["appointments:read"] },
    { "operation_id": "appointments_create", "method": "POST", "path": "/appointments", "tag": "Appointments", "tier": "beta", "destructive": false, "writes": true, "summary": "Book an appointment", "scopes": ["appointments:write"] },
    { "operation_id": "appointments_list", "method": "GET", "path": "/appointments", "tag": "Appointments", "tier": "beta", "destructive": false, "writes": false, "summary": "List appointments", "scopes": ["appointments:read"] },
    { "operation_id": "contact_timeline_list", "method": "GET", "path": "/contacts/{id}/timeline", "tag": "Contacts", "tier": "beta", "destructive": false, "writes": false, "summary": "One chronological feed of everything that happened to a contact", "scopes": ["contacts:read"] }
  ]
}

Diez operaciones, ocho de lectura y dos de escritura: crear y actualizar una cita. appointment_cancel_create no aparece. No es un error de la búsqueda: esa operación no existe para esta conexión, y el paso siguiente muestra por qué.

3. Comprueba la promesa del paquete, en vez de confiar a ciegas

POST /appointments/{id}/cancel pide appointments:delete y messages:send, dos permisos que «Agenda» nunca entrega. Pídele a tu asistente que cancele una cita y observa la respuesta real:

curl -X POST https://api.vitrinadev.com/mcp \
  -H "Authorization: Bearer $VITRINA_CONNECTOR_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"call_operation","arguments":{"operation_id":"appointment_cancel_create","params":{"id":"6ce1d84e-3740-440e-9cda-69c2ce50027c"},"body":{"reason":"prueba"}}}}'
{
  "content": [
    { "type": "text", "text": "Unknown operation 'appointment_cancel_create'. Call search_operations to see what this credential can reach. Writes are granted per domain on the authorization screen, so an operation that exists in the documentation may still be outside this connection." }
  ],
  "isError": true
}

No responde 403: responde que la operación no existe. Es la misma frase para un área que jamás autorizaste, como Leads:

{
  "content": [
    { "type": "text", "text": "Unknown operation 'leads_create'. Call search_operations to see what this credential can reach. Writes are granted per domain on the authorization screen, so an operation that exists in the documentation may still be outside this connection." }
  ],
  "isError": true
}

Tres causas distintas, un solo texto: la operación no existe, existe pero está fuera de tu workspace, o pertenece a un área que nunca autorizaste.

4. Deja que reserve una cita de verdad

appointments_create sí está en la lista del paso 2, y sí escribe. Le pides a tu asistente que agende, y por debajo llama esto:

curl -X POST https://api.vitrinadev.com/mcp \
  -H "Authorization: Bearer $VITRINA_CONNECTOR_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{
    "jsonrpc": "2.0", "id": 3, "method": "tools/call",
    "params": {
      "name": "call_operation",
      "arguments": {
        "operation_id": "appointments_create",
        "body": {
          "starts_at": "2026-10-05T15:00:00.000Z",
          "ends_at": "2026-10-05T15:30:00.000Z",
          "kind": "external",
          "appointment_type_id": "eed63545-0f09-47a3-9d14-d844ba1b0cd1",
          "contact_id": "01a0cbfe-0e3a-7960-9901-ad153b1db135",
          "location_id": "6812d9f0-9bed-44b5-91df-4e5b90941f6b",
          "customer_name": "Camila Fuentes"
        }
      }
    }
  }'
{
  "data": {
    "id": "2c595afc-1420-4e9f-a0f2-9cf8160bf825",
    "contact_id": "01a0cbfe-0e3a-7960-9901-ad153b1db135",
    "location_id": "6812d9f0-9bed-44b5-91df-4e5b90941f6b",
    "kind": "external",
    "status": "confirmed",
    "starts_at": "2026-10-05T15:00:00.000Z",
    "ends_at": "2026-10-05T15:30:00.000Z",
    "customer_name": "Camila Fuentes",
    "display_id": "A-15",
    "created_at": "2026-09-23T11:52:30.303Z"
  }
}

A-15, confirmed, sin que nadie haya abierto la aplicación. La llamada pasó por la misma ruta que cualquier cliente REST, con tu misma credencial: los mismos permisos, la misma validación, el mismo registro de auditoría. Si tu asistente repite esta llamada por un reintento, no crea una segunda cita: la respuesta idéntica se reutiliza en vez de escribir dos veces. El mecanismo completo, y cómo forzar una segunda escritura a propósito, está en Llamar la API completa desde tu IA.

Cuando falla

Unknown operation es siempre el mismo mensaje, y cubre tres causas distintas: la operación no existe, no está publicada, o existe pero ningún paquete que autorizaste la alcanza. Usa search_operations para saber qué puedes llamar: si una operación no sale ahí, no se puede llamar, sea cual sea el motivo.

Si el rol de quien autorizó se reduce después de conectar, la conexión se reduce en su siguiente llamada. Si algo que antes funcionaba empieza a responder Unknown operation, revisa primero el rol de quien autorizó la conexión.

En la aplicación: los mismos paquetes de escritura, con su descripción completa, se autorizan desde Conexiones → MCP. Guía completa en Manual de plataforma → Conectar tu IA y darle permiso.

La lista completa, paquete por paquete, está en Herramientas del conector. Las siete áreas que pueden autorizarse, y el resto del mecanismo, están en Llamar la API completa desde tu IA. Si lo que necesitas es solo leer, sin autorizar ninguna escritura, Conectar Claude a tu agenda hace ese recorrido más corto.

En esta página