{"openapi":"3.1.0","info":{"title":"Resonalia API","version":"0.1.0","description":"Multi-tenant call-automation backend.\n\n**Tenancy.** Every resource is scoped by `tenantId` and resolved through three layers:\n`global ⊕ tenantType ⊕ tenant`. Labels, field schemas, dashboard columns and the tool\nregistry all come from `GET /api/tenants/{tenantId}` — nothing about a vertical belongs\nin the UI code.\n\n**Auth.** Dashboard endpoints take `Authorization: Bearer <api key>` (a tenant key, or the\nadmin key). MCP and tool endpoints take a *signed tenant-context token* instead — see\n`POST /api/tenants/{tenantId}/token`. Webhooks authenticate by provider signature.\n\n**Live updates.** `GET /api/tenants/{tenantId}/stream` is a Server-Sent Events feed of the\ntenant's event log; the bookings table and ladder timeline are meant to be driven from it.","contact":{"name":"Resonalia","url":"https://resonalia.com"}},"servers":[{"url":"https://api.resonalia.com","description":"This deployment"}],"tags":[{"name":"Tenancy","description":"Tenants, verticals, resolved configuration"},{"name":"CRM","description":"Inbound events from a CRM (or the dashboard's mock trigger)"},{"name":"Bookings","description":"Appointments and the confirmation ladder"},{"name":"Contacts"},{"name":"Resources"},{"name":"Calls"},{"name":"Live","description":"Server-sent events"},{"name":"Agent","description":"MCP server and tool registry"},{"name":"Webhooks","description":"n8n, ElevenLabs, Twilio"},{"name":"Ops"}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Tenant API key or admin API key. `X-Api-Key` is accepted as an alternative header."}},"schemas":{"CrmEvent":{"type":"object","properties":{"eventType":{"type":"string","enum":["confirm_next_day_appointment","appointment_created","appointment_updated","appointment_cancelled","contact_updated"],"description":"What the CRM is telling us happened."},"patientNumber":{"description":"Caller's number in E.164. Alias of contact.phone.","type":"string","pattern":"^\\+[1-9]\\d{6,14}$"},"contact":{"type":"object","properties":{"name":{"type":"string","minLength":1},"phone":{"type":"string","pattern":"^\\+[1-9]\\d{6,14}$"},"locale":{"type":"string"},"externalId":{"type":"string"},"data":{"description":"Vertical-specific contact fields.","type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}}},"appointment":{"type":"object","properties":{"externalId":{"description":"The CRM's own id — our idempotency anchor.","type":"string"},"datetime":{"description":"Appointment start, ISO-8601 with offset.","anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},{"type":"string","format":"date-time"}]},"endDatetime":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},{"type":"string","format":"date-time"}]},"durationMinutes":{"type":"integer","minimum":5,"maximum":480},"resource":{"type":"string"},"doctor":{"description":"Clinic wording for `resource`.","type":"string"},"notary":{"description":"Notary wording for `resource`.","type":"string"},"location":{"type":"string"},"notes":{"type":"string"},"data":{"description":"Vertical-specific booking fields.","type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"additionalProperties":{}},"idempotencyKey":{"type":"string","maxLength":200},"runNow":{"type":"boolean"}},"required":["eventType"],"additionalProperties":{}},"CrmEventResponse":{"type":"object","properties":{"ok":{"type":"boolean","const":true},"eventId":{"type":"string"},"duplicate":{"type":"boolean"},"tenantId":{"type":"string"},"contactId":{"type":["string","null"]},"bookingId":{"type":["string","null"]},"resourceMatch":{"type":"object","properties":{"status":{"type":"string","enum":["matched","ambiguous","no_match","not_requested"]},"resourceId":{"type":["string","null"]},"label":{"type":["string","null"]},"options":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"},"score":{"type":"number"}},"required":["id","label","score"],"additionalProperties":false}}},"required":["status","resourceId","label"],"additionalProperties":false},"dispatch":{"type":"object","properties":{"mode":{"type":"string","enum":["n8n","direct","skipped","none"]},"detail":{"type":"string"}},"required":["mode"],"additionalProperties":false}},"required":["ok","eventId","duplicate","tenantId","contactId","bookingId"],"additionalProperties":false},"N8nOutcome":{"type":"object","properties":{"tenantId":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[a-z0-9][a-z0-9-_]*$"},"bookingId":{"type":"string","minLength":1,"maxLength":128},"attemptNo":{"description":"Defaults to the booking's current attempt.","type":"integer","minimum":0,"maximum":9007199254740991},"channel":{"default":"voice","type":"string","enum":["voice","sms","whatsapp","manual"]},"result":{"type":"string","enum":["confirmed","declined","rescheduled","no_answer","busy","failed","voicemail","sms_sent","sms_no_reply","sms_unparsed","escalated_manual"]},"detail":{"type":"string","maxLength":2000},"callId":{"type":"string","minLength":1,"maxLength":128},"conversationId":{"type":"string"},"occurredAt":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},{"type":"string","format":"date-time"}]},"idempotencyKey":{"type":"string","maxLength":200},"requestedReschedule":{"type":"object","properties":{"start":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},{"type":"string","format":"date-time"}]},"note":{"type":"string"}}},"data":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"execute":{"type":"boolean"}},"required":["tenantId","bookingId","result"]},"N8nOutcomeResponse":{"type":"object","properties":{"ok":{"type":"boolean","const":true},"duplicate":{"type":"boolean"},"bookingId":{"type":"string"},"status":{"type":"string","enum":["pending","confirmation_in_progress","confirmed","declined","rescheduled","unreachable","cancelled"]},"outcomeId":{"type":"string"},"decision":{"type":"object","properties":{"nextStatus":{"type":"string","enum":["pending","confirmation_in_progress","confirmed","declined","rescheduled","unreachable","cancelled"]},"action":{"type":"string","enum":["none","await_sms","send_sms","send_clarification_sms","retry_call","manual_review"]},"nextAttemptAt":{"type":["string","null"]},"reason":{"type":"string"}},"required":["nextStatus","action","nextAttemptAt","reason"],"additionalProperties":false}},"required":["ok","duplicate","bookingId","status","outcomeId","decision"],"additionalProperties":false},"Booking":{"type":"object","properties":{"_id":{"type":"string"},"tenantId":{"type":"string"},"contactId":{"type":"string"},"resourceId":{"type":["string","null"]},"start":{"type":"string","description":"ISO-8601 timestamp"},"end":{"type":"string","description":"ISO-8601 timestamp"},"status":{"type":"string","enum":["pending","confirmation_in_progress","confirmed","declined","rescheduled","unreachable","cancelled"]},"source":{"type":"string"},"externalId":{"type":["string","null"]},"location":{"type":["string","null"]},"notes":{"type":["string","null"]},"confirmation":{"type":"object","properties":{"attemptNo":{"type":"number"},"lastAttemptAt":{"anyOf":[{"type":"string","description":"ISO-8601 timestamp"},{"type":"null"}]},"nextAttemptAt":{"anyOf":[{"type":"string","description":"ISO-8601 timestamp"},{"type":"null"}]},"channel":{"type":["string","null"]},"needsManualReview":{"type":"boolean"}},"required":["attemptNo","needsManualReview"],"additionalProperties":false},"statusHistory":{"type":"array","items":{"type":"object","properties":{"from":{"type":"string","enum":["pending","confirmation_in_progress","confirmed","declined","rescheduled","unreachable","cancelled"]},"to":{"type":"string","enum":["pending","confirmation_in_progress","confirmed","declined","rescheduled","unreachable","cancelled"]},"at":{"type":"string","description":"ISO-8601 timestamp"},"by":{"type":"string"},"reason":{"type":"string"}},"required":["to","at","by"],"additionalProperties":false}},"data":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"contact":{"anyOf":[{"type":"object","properties":{"_id":{"type":"string"},"name":{"type":"string"},"phone":{"type":"string"}},"required":["_id","name","phone"],"additionalProperties":false},{"type":"null"}]},"resource":{"anyOf":[{"type":"object","properties":{"_id":{"type":"string"},"name":{"type":"string"},"kind":{"type":"string"}},"required":["_id","name","kind"],"additionalProperties":false},{"type":"null"}]},"createdAt":{"type":"string","description":"ISO-8601 timestamp"},"updatedAt":{"type":"string","description":"ISO-8601 timestamp"}},"required":["_id","tenantId","contactId","start","end","status","source","confirmation","statusHistory","data","createdAt","updatedAt"],"additionalProperties":false},"BookingInput":{"type":"object","properties":{"contactId":{"type":"string","minLength":1,"maxLength":128},"contact":{"type":"object","properties":{"phone":{"type":"string","pattern":"^\\+[1-9]\\d{6,14}$"},"name":{"type":"string","minLength":1},"locale":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}},"externalIds":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"}},"data":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["phone","name"]},"resourceId":{"type":"string","minLength":1,"maxLength":128},"start":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},{"type":"string","format":"date-time"}]},"end":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},{"type":"string","format":"date-time"}]},"durationMinutes":{"type":"integer","minimum":5,"maximum":480},"status":{"type":"string","enum":["pending","confirmation_in_progress","confirmed","declined","rescheduled","unreachable","cancelled"]},"source":{"type":"string","enum":["agent","crm","ui","n8n","seed","api"]},"externalId":{"type":"string"},"location":{"type":"string"},"notes":{"type":"string"},"data":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["start"]},"BookingPatch":{"type":"object","properties":{"status":{"type":"string","enum":["pending","confirmation_in_progress","confirmed","declined","rescheduled","unreachable","cancelled"]},"start":{"anyOf":[{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},{"type":"string","format":"date-time"}]},"durationMinutes":{"type":"integer","minimum":5,"maximum":480},"resourceId":{"type":"string","minLength":1,"maxLength":128},"location":{"type":"string"},"notes":{"type":"string"},"data":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"reason":{"type":"string"}}},"Contact":{"type":"object","properties":{"_id":{"type":"string"},"tenantId":{"type":"string"},"phone":{"type":"string"},"name":{"type":"string"},"locale":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}},"externalIds":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"}},"data":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"consent":{"type":"object","properties":{"sms":{"type":"boolean"},"voice":{"type":"boolean"},"whatsapp":{"type":"boolean"}},"required":["sms","voice","whatsapp"],"additionalProperties":false},"lastContactedAt":{"anyOf":[{"type":"string","description":"ISO-8601 timestamp"},{"type":"null"}]},"createdAt":{"type":"string","description":"ISO-8601 timestamp"},"updatedAt":{"type":"string","description":"ISO-8601 timestamp"}},"required":["_id","tenantId","phone","name","tags","externalIds","data","consent","createdAt","updatedAt"],"additionalProperties":false},"ContactInput":{"type":"object","properties":{"phone":{"type":"string","pattern":"^\\+[1-9]\\d{6,14}$"},"name":{"type":"string","minLength":1},"locale":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}},"externalIds":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"}},"data":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["phone","name"]},"Resource":{"type":"object","properties":{"_id":{"type":"string"},"tenantId":{"type":"string"},"name":{"type":"string"},"kind":{"type":"string"},"active":{"type":"boolean"},"aliases":{"type":"array","items":{"type":"string"}},"availability":{"type":"object","properties":{"slotMinutes":{"type":"number"},"weekly":{"type":"array","items":{"type":"object","properties":{"day":{"type":"number"},"start":{"type":"string"},"end":{"type":"string"}},"required":["day","start","end"],"additionalProperties":false}},"exceptions":{"type":"array","items":{"type":"object","properties":{"date":{"type":"string"},"closed":{"type":"boolean"},"start":{"type":"string"},"end":{"type":"string"}},"required":["date","closed"],"additionalProperties":false}}},"required":["slotMinutes","weekly","exceptions"],"additionalProperties":false},"data":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"createdAt":{"type":"string","description":"ISO-8601 timestamp"},"updatedAt":{"type":"string","description":"ISO-8601 timestamp"}},"required":["_id","tenantId","name","kind","active","aliases","availability","data","createdAt","updatedAt"],"additionalProperties":false},"ResourceInput":{"type":"object","properties":{"name":{"type":"string","minLength":1},"kind":{"type":"string"},"active":{"type":"boolean"},"aliases":{"type":"array","items":{"type":"string"}},"availability":{"type":"object","properties":{"slotMinutes":{"type":"integer","minimum":5,"maximum":240},"weekly":{"type":"array","items":{"type":"object","properties":{"day":{"type":"integer","minimum":0,"maximum":6},"start":{"type":"string","pattern":"^([01]\\d|2[0-3]):[0-5]\\d$"},"end":{"type":"string","pattern":"^([01]\\d|2[0-3]):[0-5]\\d$"}},"required":["day","start","end"]}},"exceptions":{"type":"array","items":{"type":"object","properties":{"date":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"closed":{"default":false,"type":"boolean"},"start":{"type":"string","pattern":"^([01]\\d|2[0-3]):[0-5]\\d$"},"end":{"type":"string","pattern":"^([01]\\d|2[0-3]):[0-5]\\d$"}},"required":["date"]}}}},"data":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["name"]},"Call":{"type":"object","properties":{"_id":{"type":"string"},"tenantId":{"type":"string"},"direction":{"type":"string","enum":["inbound","outbound"]},"from":{"type":"string"},"to":{"type":"string"},"status":{"type":"string"},"provider":{"type":"string"},"providerCallSid":{"type":["string","null"]},"conversationId":{"type":["string","null"]},"agentId":{"type":["string","null"]},"contactId":{"type":["string","null"]},"bookingId":{"type":["string","null"]},"attemptNo":{"type":["number","null"]},"startedAt":{"anyOf":[{"type":"string","description":"ISO-8601 timestamp"},{"type":"null"}]},"endedAt":{"anyOf":[{"type":"string","description":"ISO-8601 timestamp"},{"type":"null"}]},"durationSeconds":{"type":["number","null"]},"recordingUrl":{"type":["string","null"]},"transcript":{"type":"array","items":{"type":"object","properties":{"role":{"type":"string"},"text":{"type":"string"},"at":{"anyOf":[{"type":"string","description":"ISO-8601 timestamp"},{"type":"null"}]}},"required":["role","text"],"additionalProperties":false}},"summary":{"type":["string","null"]},"createdAt":{"type":"string","description":"ISO-8601 timestamp"},"updatedAt":{"type":"string","description":"ISO-8601 timestamp"}},"required":["_id","tenantId","direction","from","to","status","provider","transcript","createdAt","updatedAt"],"additionalProperties":false},"Event":{"type":"object","properties":{"_id":{"type":"string"},"tenantId":{"type":"string"},"type":{"type":"string"},"source":{"type":"string"},"payload":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"contactId":{"type":["string","null"]},"bookingId":{"type":["string","null"]},"callId":{"type":["string","null"]},"outcomeId":{"type":["string","null"]},"createdAt":{"type":"string","description":"ISO-8601 timestamp"}},"required":["_id","tenantId","type","source","payload","createdAt"],"additionalProperties":false},"Outcome":{"type":"object","properties":{"_id":{"type":"string"},"tenantId":{"type":"string"},"bookingId":{"type":"string"},"attemptNo":{"type":"number"},"channel":{"type":"string","enum":["voice","sms","whatsapp","manual"]},"result":{"type":"string","enum":["confirmed","declined","rescheduled","no_answer","busy","failed","voicemail","sms_sent","sms_no_reply","sms_unparsed","escalated_manual"]},"detail":{"type":["string","null"]},"callId":{"type":["string","null"]},"source":{"type":"string"},"idempotencyKey":{"type":"string"},"occurredAt":{"type":"string","description":"ISO-8601 timestamp"},"createdAt":{"type":"string","description":"ISO-8601 timestamp"}},"required":["_id","tenantId","bookingId","attemptNo","channel","result","source","idempotencyKey","occurredAt","createdAt"],"additionalProperties":false},"Slot":{"type":"object","properties":{"start":{"type":"string","description":"ISO-8601 timestamp"},"end":{"type":"string","description":"ISO-8601 timestamp"},"resourceId":{"type":"string"},"resourceName":{"type":"string"},"spoken":{"type":"string","description":"Human/voice-friendly rendering in the tenant's locale."}},"required":["start","end","resourceId","resourceName","spoken"],"additionalProperties":false},"TenantSummary":{"type":"object","properties":{"_id":{"type":"string"},"name":{"type":"string"},"type":{"type":"string"},"status":{"type":"string","enum":["active","disabled"]},"locale":{"type":"string"},"timezone":{"type":"string"},"branding":{"type":"object","properties":{"displayName":{"type":"string"},"primaryColor":{"type":"string"},"logoUrl":{"type":"string"}},"additionalProperties":false},"labels":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"}},"numbers":{"type":"array","items":{"type":"string"}}},"required":["_id","name","type","status","locale","timezone","branding","labels","numbers"],"additionalProperties":false},"TenantConfig":{"type":"object","properties":{"_id":{"type":"string"},"name":{"type":"string"},"type":{"type":"string"},"status":{"type":"string","enum":["active","disabled"]},"locale":{"type":"string"},"timezone":{"type":"string"},"branding":{"type":"object","properties":{"displayName":{"type":"string"},"primaryColor":{"type":"string"},"logoUrl":{"type":"string"}},"additionalProperties":false},"labels":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"}},"numbers":{"type":"array","items":{"type":"string"}},"fields":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"dashboard":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"catalog":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"array","items":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}}},"confirmation":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"tools":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"},"layer":{"type":"string"}},"required":["name","description","layer"],"additionalProperties":false}},"business":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["_id","name","type","status","locale","timezone","branding","labels","numbers","fields","dashboard","catalog","confirmation","tools","business"],"additionalProperties":false},"TenantType":{"type":"object","properties":{"_id":{"type":"string"},"key":{"type":"string"},"name":{"type":"string"},"labels":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"}},"fields":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"dashboard":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"defaultTools":{"type":"array","items":{"type":"string"}}},"required":["_id","key","name","labels","fields","dashboard","defaultTools"],"additionalProperties":false},"TenantCreate":{"type":"object","properties":{"_id":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[a-z0-9][a-z0-9-_]*$","description":"Tenant id — stable, lowercase, used in every URL."},"name":{"type":"string","minLength":1},"type":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[a-z0-9][a-z0-9-_]*$","description":"Tenant type key: clinic, notary, …"},"locale":{"type":"string"},"timezone":{"type":"string"},"branding":{"type":"object","properties":{"displayName":{"type":"string"},"primaryColor":{"type":"string"},"logoUrl":{"type":"string","format":"uri"}}},"business":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"integrations":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"confirmation":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"toolOverrides":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"vocabulary":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["_id","name","type"]},"TenantPatch":{"type":"object","properties":{"name":{"type":"string","minLength":1},"type":{"type":"string","minLength":1,"maxLength":64,"pattern":"^[a-z0-9][a-z0-9-_]*$","description":"Tenant type key: clinic, notary, …"},"locale":{"type":"string"},"timezone":{"type":"string"},"branding":{"type":"object","properties":{"displayName":{"type":"string"},"primaryColor":{"type":"string"},"logoUrl":{"type":"string","format":"uri"}}},"business":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"integrations":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"confirmation":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"toolOverrides":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"vocabulary":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}}},"TriggerConfirmation":{"type":"object","properties":{"mode":{"default":"auto","type":"string","enum":["auto","n8n","direct"]},"reason":{"type":"string"}}},"ApiKeyCreate":{"type":"object","properties":{"name":{"type":"string","minLength":1},"scopes":{"default":["read","write"],"type":"array","items":{"type":"string","enum":["read","write","admin"]}}},"required":["name"]},"ApiKeyCreated":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"scopes":{"type":"array","items":{"type":"string"}},"apiKey":{"type":"string"}},"required":["id","name","scopes","apiKey"],"additionalProperties":false},"TenantToken":{"type":"object","properties":{"tenantId":{"type":"string"},"token":{"type":"string","description":"Signed tenant-context token for MCP / tool calls."},"mcpUrl":{"type":"string"},"expiresAt":{"type":["string","null"]}},"required":["tenantId","token","mcpUrl","expiresAt"],"additionalProperties":false},"Error":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{},"requestId":{"type":"string"}},"required":["code","message","requestId"],"additionalProperties":false}},"required":["error"],"additionalProperties":false},"Ok":{"type":"object","properties":{"ok":{"type":"boolean","const":true}},"required":["ok"],"additionalProperties":false}}},"paths":{"/api/health":{"get":{"tags":["Ops"],"summary":"Liveness and MongoDB connectivity","security":[],"responses":{"200":{"description":"Healthy","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string"},"service":{"type":"string"},"db":{"type":"string","enum":["up","down"]},"time":{"type":"string","format":"date-time"}}}}}},"503":{"description":"Degraded","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/api/tenant-types":{"get":{"tags":["Tenancy"],"summary":"List verticals","description":"Labels, field schemas and default tools per vertical. Admin key required.","responses":{"200":{"description":"Known verticals","content":{"application/json":{"schema":{"type":"object","properties":{"tenantTypes":{"type":"array","items":{"$ref":"#/components/schemas/TenantType"}}},"required":["tenantTypes"]}}}},"401":{"description":"Missing or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/tenants":{"get":{"tags":["Tenancy"],"summary":"List tenants (tenant switcher)","responses":{"200":{"description":"All tenants","content":{"application/json":{"schema":{"type":"object","properties":{"tenants":{"type":"array","items":{"$ref":"#/components/schemas/TenantSummary"}}},"required":["tenants"]}}}},"401":{"description":"Missing or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Tenancy"],"summary":"Create a tenant","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TenantCreate"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Conflict / illegal status transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/tenants/{tenantId}":{"parameters":[{"name":"tenantId","in":"path","required":true,"schema":{"type":"string"},"description":"Tenant id, e.g. `clinica-demo`."}],"get":{"tags":["Tenancy"],"summary":"Resolved tenant configuration","description":"Everything the UI needs to render itself for this tenant: labels, compiled field schemas, dashboard columns, catalogs, confirmation settings and the resolved tool list.","responses":{"200":{"description":"Resolved configuration","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TenantConfig"}}}},"401":{"description":"Missing or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Tenancy"],"summary":"Update a tenant (admin)","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TenantPatch"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ok"}}}},"401":{"description":"Missing or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/tenants/{tenantId}/events":{"parameters":[{"name":"tenantId","in":"path","required":true,"schema":{"type":"string"},"description":"Tenant id, e.g. `clinica-demo`."}],"get":{"tags":["CRM"],"summary":"Event log (newest first)","parameters":[{"name":"bookingId","in":"query","required":false,"schema":{"type":"string","minLength":1,"maxLength":128}},{"name":"type","in":"query","required":false,"schema":{"type":"string"}},{"name":"since","in":"query","required":false,"schema":{"description":"ISO-8601; returns events created after this instant.","type":"string"},"description":"ISO-8601; returns events created after this instant."},{"name":"limit","in":"query","required":false,"schema":{"default":50,"type":"integer","minimum":1,"maximum":200}}],"responses":{"200":{"description":"Events","content":{"application/json":{"schema":{"type":"object","properties":{"events":{"type":"array","items":{"$ref":"#/components/schemas/Event"}}},"required":["events"]}}}},"401":{"description":"Missing or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["CRM"],"summary":"Ingest a CRM event","description":"The production contract. Validates, upserts contact + booking, appends to the timeline and dispatches the tenant's workflow. `confirm_next_day_appointment` starts the confirmation ladder immediately.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CrmEvent"}}}},"responses":{"200":{"description":"Duplicate (idempotencyKey already seen)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CrmEventResponse"}}}},"201":{"description":"Accepted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CrmEventResponse"}}}},"401":{"description":"Missing or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/tenants/{tenantId}/bookings":{"parameters":[{"name":"tenantId","in":"path","required":true,"schema":{"type":"string"},"description":"Tenant id, e.g. `clinica-demo`."}],"get":{"tags":["Bookings"],"summary":"List bookings","parameters":[{"name":"status","in":"query","required":false,"schema":{"description":"Repeatable.","anyOf":[{"type":"string","enum":["pending","confirmation_in_progress","confirmed","declined","rescheduled","unreachable","cancelled"]},{"type":"array","items":{"type":"string","enum":["pending","confirmation_in_progress","confirmed","declined","rescheduled","unreachable","cancelled"]}}]},"description":"Repeatable."},{"name":"from","in":"query","required":false,"schema":{"description":"ISO-8601 lower bound on start.","type":"string"},"description":"ISO-8601 lower bound on start."},{"name":"to","in":"query","required":false,"schema":{"description":"ISO-8601 upper bound on start.","type":"string"},"description":"ISO-8601 upper bound on start."},{"name":"contactId","in":"query","required":false,"schema":{"type":"string","minLength":1,"maxLength":128}},{"name":"resourceId","in":"query","required":false,"schema":{"type":"string","minLength":1,"maxLength":128}},{"name":"limit","in":"query","required":false,"schema":{"default":50,"type":"integer","minimum":1,"maximum":200}}],"responses":{"200":{"description":"Bookings with contact and resource summaries","content":{"application/json":{"schema":{"type":"object","properties":{"bookings":{"type":"array","items":{"$ref":"#/components/schemas/Booking"}}},"required":["bookings"]}}}},"401":{"description":"Missing or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Bookings"],"summary":"Create a booking","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingInput"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"type":"object","properties":{"booking":{"$ref":"#/components/schemas/Booking"},"contactId":{"type":"string"},"created":{"type":"boolean"}}}}}},"409":{"description":"Conflict / illegal status transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/tenants/{tenantId}/bookings/{bookingId}":{"parameters":[{"name":"tenantId","in":"path","required":true,"schema":{"type":"string"},"description":"Tenant id, e.g. `clinica-demo`."},{"name":"bookingId","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["Bookings"],"summary":"Get one booking with its outcomes","responses":{"200":{"description":"Booking","content":{"application/json":{"schema":{"type":"object","properties":{"booking":{"$ref":"#/components/schemas/Booking"},"outcomes":{"type":"array","items":{"$ref":"#/components/schemas/Outcome"}}}}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Bookings"],"summary":"Update a booking (status, time, resource, vertical fields)","description":"Status changes are validated against the booking state machine; an illegal transition is a 409.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingPatch"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"type":"object","properties":{"booking":{"$ref":"#/components/schemas/Booking"}}}}}},"409":{"description":"Conflict / illegal status transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/tenants/{tenantId}/bookings/{bookingId}/timeline":{"parameters":[{"name":"tenantId","in":"path","required":true,"schema":{"type":"string"},"description":"Tenant id, e.g. `clinica-demo`."},{"name":"bookingId","in":"path","required":true,"schema":{"type":"string"}}],"get":{"tags":["Bookings"],"summary":"Full ladder timeline for one booking","responses":{"200":{"description":"Timeline","content":{"application/json":{"schema":{"type":"object","properties":{"booking":{"$ref":"#/components/schemas/Booking"},"events":{"type":"array","items":{"$ref":"#/components/schemas/Event"}},"outcomes":{"type":"array","items":{"$ref":"#/components/schemas/Outcome"}},"calls":{"type":"array","items":{"$ref":"#/components/schemas/Call"}}}}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/tenants/{tenantId}/bookings/{bookingId}/confirm":{"parameters":[{"name":"tenantId","in":"path","required":true,"schema":{"type":"string"},"description":"Tenant id, e.g. `clinica-demo`."},{"name":"bookingId","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["Bookings"],"summary":"Start (or retry) the confirmation ladder","description":"Rung 1. `auto` uses n8n when the tenant has it, otherwise dials via ElevenLabs directly.","requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TriggerConfirmation"}}}},"responses":{"200":{"description":"Dispatched","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"bookingId":{"type":"string"},"mode":{"type":"string","enum":["n8n","direct","skipped"]},"status":{"type":"string"},"attemptNo":{"type":"integer"}}}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Conflict / illegal status transition","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/tenants/{tenantId}/contacts":{"parameters":[{"name":"tenantId","in":"path","required":true,"schema":{"type":"string"},"description":"Tenant id, e.g. `clinica-demo`."}],"get":{"tags":["Contacts"],"summary":"List or search contacts","parameters":[{"name":"q","in":"query","schema":{"type":"string"},"description":"Name or phone fragment."},{"name":"phone","in":"query","schema":{"type":"string"},"description":"Exact E.164 lookup."},{"name":"limit","in":"query","schema":{"type":"integer","default":50}}],"responses":{"200":{"description":"Contacts","content":{"application/json":{"schema":{"type":"object","properties":{"contacts":{"type":"array","items":{"$ref":"#/components/schemas/Contact"}}},"required":["contacts"]}}}},"401":{"description":"Missing or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Contacts"],"summary":"Create or update a contact (upsert on phone)","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactInput"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"type":"object","properties":{"contact":{"$ref":"#/components/schemas/Contact"},"created":{"type":"boolean"}}}}}},"201":{"description":"Created","content":{"application/json":{"schema":{"type":"object","properties":{"contact":{"$ref":"#/components/schemas/Contact"},"created":{"type":"boolean"}}}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/tenants/{tenantId}/resources":{"parameters":[{"name":"tenantId","in":"path","required":true,"schema":{"type":"string"},"description":"Tenant id, e.g. `clinica-demo`."}],"get":{"tags":["Resources"],"summary":"List resources (doctors, notaries, rooms)","parameters":[{"name":"kind","in":"query","schema":{"type":"string"}},{"name":"active","in":"query","schema":{"type":"string","enum":["true","false"]}},{"name":"specialty","in":"query","schema":{"type":"string"}},{"name":"limit","in":"query","schema":{"type":"integer","default":100}}],"responses":{"200":{"description":"Resources","content":{"application/json":{"schema":{"type":"object","properties":{"resources":{"type":"array","items":{"$ref":"#/components/schemas/Resource"}}},"required":["resources"]}}}},"401":{"description":"Missing or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Resources"],"summary":"Create a resource","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResourceInput"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"type":"object","properties":{"resource":{"$ref":"#/components/schemas/Resource"}}}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/tenants/{tenantId}/availability":{"parameters":[{"name":"tenantId","in":"path","required":true,"schema":{"type":"string"},"description":"Tenant id, e.g. `clinica-demo`."}],"get":{"tags":["Bookings"],"summary":"Free slots","parameters":[{"name":"resourceId","in":"query","required":false,"schema":{"type":"string","minLength":1,"maxLength":128}},{"name":"specialty","in":"query","required":false,"schema":{"type":"string"}},{"name":"from","in":"query","required":false,"schema":{"type":"string"}},{"name":"to","in":"query","required":false,"schema":{"type":"string"}},{"name":"durationMinutes","in":"query","required":false,"schema":{"type":"integer","minimum":5,"maximum":480}},{"name":"limit","in":"query","required":false,"schema":{"default":20,"type":"integer","minimum":1,"maximum":100}}],"responses":{"200":{"description":"Free slots in the tenant's timezone","content":{"application/json":{"schema":{"type":"object","properties":{"slots":{"type":"array","items":{"$ref":"#/components/schemas/Slot"}}},"required":["slots"]}}}},"401":{"description":"Missing or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/tenants/{tenantId}/calls":{"parameters":[{"name":"tenantId","in":"path","required":true,"schema":{"type":"string"},"description":"Tenant id, e.g. `clinica-demo`."}],"get":{"tags":["Calls"],"summary":"Call log","parameters":[{"name":"direction","in":"query","required":false,"schema":{"type":"string","enum":["inbound","outbound"]}},{"name":"bookingId","in":"query","required":false,"schema":{"type":"string","minLength":1,"maxLength":128}},{"name":"limit","in":"query","required":false,"schema":{"default":50,"type":"integer","minimum":1,"maximum":200}}],"responses":{"200":{"description":"Calls, newest first","content":{"application/json":{"schema":{"type":"object","properties":{"calls":{"type":"array","items":{"$ref":"#/components/schemas/Call"}}},"required":["calls"]}}}},"401":{"description":"Missing or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/tenants/{tenantId}/outcomes":{"parameters":[{"name":"tenantId","in":"path","required":true,"schema":{"type":"string"},"description":"Tenant id, e.g. `clinica-demo`."}],"get":{"tags":["Bookings"],"summary":"Confirmation outcomes","parameters":[{"name":"bookingId","in":"query","schema":{"type":"string"}},{"name":"limit","in":"query","schema":{"type":"integer","default":50}}],"responses":{"200":{"description":"Outcomes","content":{"application/json":{"schema":{"type":"object","properties":{"outcomes":{"type":"array","items":{"$ref":"#/components/schemas/Outcome"}}},"required":["outcomes"]}}}},"401":{"description":"Missing or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/tenants/{tenantId}/stream":{"parameters":[{"name":"tenantId","in":"path","required":true,"schema":{"type":"string"},"description":"Tenant id, e.g. `clinica-demo`."}],"get":{"tags":["Live"],"summary":"Server-sent event stream of the tenant's timeline","description":"Events: `ready`, `event` (an Event document), `ping`. `EventSource` cannot set headers, so this route also accepts `?apiKey=`. Pass `?since=<ISO>` to replay from a cursor.","parameters":[{"name":"apiKey","in":"query","schema":{"type":"string"}},{"name":"since","in":"query","schema":{"type":"string","format":"date-time"}}],"responses":{"200":{"description":"SSE stream","content":{"text/event-stream":{"schema":{"type":"string"}}}},"401":{"description":"Missing or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/tenants/{tenantId}/tools":{"parameters":[{"name":"tenantId","in":"path","required":true,"schema":{"type":"string"},"description":"Tenant id, e.g. `clinica-demo`."}],"get":{"tags":["Agent"],"summary":"Resolved tool registry for this tenant","responses":{"200":{"description":"Tools","content":{"application/json":{"schema":{"type":"object","properties":{"tenantId":{"type":"string"},"tools":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"description":{"type":"string"},"layer":{"type":"string","enum":["global","type","tenant"]},"readOnly":{"type":"boolean"},"inputSchema":{"type":"object"}}}}}}}}},"401":{"description":"Missing or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/tenants/{tenantId}/token":{"parameters":[{"name":"tenantId","in":"path","required":true,"schema":{"type":"string"},"description":"Tenant id, e.g. `clinica-demo`."}],"post":{"tags":["Agent"],"summary":"Mint a signed tenant-context token (admin)","description":"The token an ElevenLabs agent or MCP client presents on every tool call. It carries the tenant — and optionally the conversation and caller ID — so no tool ever trusts a tenant id from the model.","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"ttlSeconds":{"type":"integer","minimum":60},"conversationId":{"type":"string"},"scopes":{"type":"array","items":{"type":"string","enum":["mcp","tools","webhook"]}}}}}}},"responses":{"200":{"description":"Token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TenantToken"}}}},"401":{"description":"Missing or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/tenants/{tenantId}/api-keys":{"parameters":[{"name":"tenantId","in":"path","required":true,"schema":{"type":"string"},"description":"Tenant id, e.g. `clinica-demo`."}],"get":{"tags":["Tenancy"],"summary":"List a tenant's API keys (metadata only)","responses":{"200":{"description":"Keys","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Tenancy"],"summary":"Create a tenant API key (admin)","description":"The plaintext key is returned exactly once; only its hash is stored.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyCreate"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyCreated"}}}},"401":{"description":"Missing or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/tenants/{tenantId}/agent/sync":{"parameters":[{"name":"tenantId","in":"path","required":true,"schema":{"type":"string"},"description":"Tenant id, e.g. `clinica-demo`."}],"post":{"tags":["Agent"],"summary":"Push prompt, greeting and ASR keywords to the tenant's ElevenLabs agent","responses":{"200":{"description":"Synced","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"501":{"description":"Not configured","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/mcp":{"post":{"tags":["Agent"],"summary":"MCP endpoint (Streamable HTTP, stateless)","description":"JSON-RPC 2.0. Supports `initialize`, `tools/list`, `tools/call`, `ping`. Authenticate with a signed tenant-context token in `Authorization: Bearer`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"description":"JSON-RPC response","content":{"application/json":{"schema":{"type":"object"}}}},"202":{"description":"Notification accepted"},"401":{"description":"Missing or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"get":{"tags":["Agent"],"summary":"MCP endpoint discovery","security":[],"responses":{"200":{"description":"Server info","content":{"application/json":{"schema":{"type":"object"}}}}}}},"/api/mcp/{token}":{"parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string"}}],"post":{"tags":["Agent"],"summary":"MCP endpoint with the tenant token in the path","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"description":"JSON-RPC response","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/tools/{name}":{"parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string"},"description":"Tool name, e.g. `get_availability`."}],"post":{"tags":["Agent"],"summary":"Call one tool over plain REST (ElevenLabs webhook tools)","description":"Same registry, same validation as MCP. Auth: signed tenant-context token.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"text":{"type":"string"},"data":{"type":"object"}}}}}},"401":{"description":"Missing or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/webhooks/n8n/outcome":{"post":{"tags":["Webhooks"],"summary":"Ladder outcome from n8n","description":"Signed with `x-resonalia-signature: t=<unix>,v1=<hmac-sha256 of `${t}.${body}`>` (or the shared `x-resonalia-key`). Idempotent on bookingId + attemptNo + channel + result.","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/N8nOutcome"}}}},"responses":{"200":{"description":"Recorded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/N8nOutcomeResponse"}}}},"401":{"description":"Missing or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validation failed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/webhooks/elevenlabs/post-call":{"post":{"tags":["Webhooks"],"summary":"ElevenLabs post-call payload (transcript, summary, data collection)","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"description":"Stored","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/webhooks/elevenlabs/personalization":{"post":{"tags":["Webhooks"],"summary":"Conversation-initiation webhook","description":"Resolves the tenant from the called number / agent id, mints a call-scoped tenant token and returns dynamic variables plus a personalised greeting.","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object"}}}},"responses":{"200":{"description":"conversation_initiation_client_data","content":{"application/json":{"schema":{"type":"object"}}}},"404":{"description":"Not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/webhooks/twilio/voice":{"post":{"tags":["Webhooks"],"summary":"Inbound voice → TwiML bridge into the tenant's agent","security":[],"requestBody":{"required":true,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object"}}}},"responses":{"200":{"description":"TwiML","content":{"text/xml":{"schema":{"type":"string"}}}},"401":{"description":"Missing or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/webhooks/twilio/sms":{"post":{"tags":["Webhooks"],"summary":"Inbound SMS (DA / NU reply grammar)","security":[],"requestBody":{"required":true,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object"}}}},"responses":{"200":{"description":"TwiML","content":{"text/xml":{"schema":{"type":"string"}}}},"401":{"description":"Missing or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/webhooks/twilio/status":{"post":{"tags":["Webhooks"],"summary":"Call status callback (drives no-answer escalation)","security":[],"requestBody":{"required":true,"content":{"application/x-www-form-urlencoded":{"schema":{"type":"object"}}}},"responses":{"200":{"description":"Recorded","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/scheduler/tick":{"post":{"tags":["Ops"],"summary":"Advance every ladder whose next step is due (cron)","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"tenantId":{"type":"string"},"limit":{"type":"integer"}}}}}},"responses":{"200":{"description":"Tick result","content":{"application/json":{"schema":{"type":"object"}}}},"401":{"description":"Missing or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/admin/indexes":{"post":{"tags":["Ops"],"summary":"Ensure MongoDB indexes (idempotent, safe on every deploy)","responses":{"200":{"description":"Done","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Ok"}}}},"401":{"description":"Missing or invalid credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}