{"openapi":"3.0.3","info":{"title":"Mivicall API","version":"1.0.0","description":"API REST para integração de Practice Management Systems (PMS) com a recepcionista AI Mivicall.\n\nPadrão de autenticação: Bearer com API key (`miv_live_*` em produção, `miv_test_*` em sandbox).\n\nPara guias passo-a-passo, ver [docs.mivicall.com/guides/pms-integration](https://docs.mivicall.com/guides/pms-integration).","contact":{"name":"Mivicall Support","email":"support@mivicall.com","url":"https://docs.mivicall.com"},"license":{"name":"Proprietary"}},"servers":[{"url":"https://api.mivicall.com","description":"Produção"}],"security":[{"bearerAuth":[]}],"tags":[{"name":"Tenant","description":"Dados da clínica autenticada"},{"name":"Appointments","description":"Marcações — leitura, criação, status updates"},{"name":"Professionals","description":"Médicos / profissionais clínicos"},{"name":"Services","description":"Tipos de consulta + preços"},{"name":"Calls","description":"Chamadas Telnyx — listagem, transcripts, recordings"},{"name":"Health","description":"Healthchecks (público)"}],"paths":{"/health":{"get":{"tags":["Health"],"summary":"Healthcheck básico","description":"Liveness probe. Sempre 200 enquanto o serviço estiver up.","security":[],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"ok"},"service":{"type":"string","example":"atende-api"},"timestamp":{"type":"string","format":"date-time"},"requestId":{"type":"string","format":"uuid"}}}}}}}}},"/v1/tenants/me":{"get":{"tags":["Tenant"],"summary":"Get current tenant","description":"Devolve dados da clínica associada à API key.","responses":{"200":{"description":"Dados do tenant","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Tenant"}}}},"400":{"description":"Payload mal-formado","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"API key inválida ou em falta","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope insuficiente","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Recurso inexistente","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validação Zod falhou","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit excedido","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Segundos a esperar antes de retentar"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Tenant"],"summary":"Update tenant config","description":"Actualizar configuração da clínica (nome, voz, slug público, etc.). Campos não-presentes ficam inalterados.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"specialty":{"type":"string"},"publicSlug":{"type":"string","nullable":true},"publicBookingEnabled":{"type":"boolean"},"config":{"type":"object","additionalProperties":true}}}}}},"responses":{"200":{"description":"Tenant actualizado","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Tenant"}}}},"400":{"description":"Payload mal-formado","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"API key inválida ou em falta","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope insuficiente","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Recurso inexistente","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validação Zod falhou","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit excedido","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Segundos a esperar antes de retentar"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/appointments":{"get":{"tags":["Appointments"],"summary":"List appointments","description":"Listar marcações com filtros opcionais.","parameters":[{"name":"from","in":"query","schema":{"type":"string","format":"date-time"},"description":"Inclusivo. ISO 8601 com timezone."},{"name":"to","in":"query","schema":{"type":"string","format":"date-time"},"description":"Exclusivo. ISO 8601 com timezone."},{"name":"professionalId","in":"query","schema":{"type":"string","format":"uuid"}},{"name":"status","in":"query","schema":{"type":"string","enum":["confirmed","attended","no_show","cancelled","rescheduled"]}}],"responses":{"200":{"description":"Lista de marcações","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Appointment"}}}}}}},"400":{"description":"Payload mal-formado","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"API key inválida ou em falta","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope insuficiente","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Recurso inexistente","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validação Zod falhou","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit excedido","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Segundos a esperar antes de retentar"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"post":{"tags":["Appointments"],"summary":"Create appointment","description":"Criar marcação manualmente (ex: agendada pelo PMS). Usar `Idempotency-Key` header para evitar duplicados em caso de retry.","parameters":[{"name":"Idempotency-Key","in":"header","schema":{"type":"string"},"description":"Identificador único para retry-safe. Ex: `pms-create-{vosso-id}`."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["professionalId","serviceTypeId","patientName","patientPhone","startsAt"],"properties":{"professionalId":{"type":"string","format":"uuid"},"serviceTypeId":{"type":"string","format":"uuid"},"patientName":{"type":"string","example":"Maria Silva"},"patientPhone":{"type":"string","pattern":"^\\+[1-9]\\d{1,14}$","example":"+351912345678","description":"E.164 format"},"startsAt":{"type":"string","format":"date-time","example":"2026-05-14T10:00:00+01:00"},"patientNotes":{"type":"string"}}}}}},"responses":{"201":{"description":"Marcação criada","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Appointment"}}}},"400":{"description":"Payload mal-formado","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"API key inválida ou em falta","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope insuficiente","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Recurso inexistente","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Conflito de slot (outro paciente já marcou esse horário)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validação Zod falhou","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit excedido","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Segundos a esperar antes de retentar"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/appointments/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"tags":["Appointments"],"summary":"Get appointment","responses":{"200":{"description":"Detalhe da marcação","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Appointment"}}}},"400":{"description":"Payload mal-formado","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"API key inválida ou em falta","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope insuficiente","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Recurso inexistente","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validação Zod falhou","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit excedido","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Segundos a esperar antes de retentar"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"patch":{"tags":["Appointments"],"summary":"Update appointment","description":"Editar marcação. Usos comuns:\n\n- `{ \"status\": \"attended\" }` quando paciente faz check-in no PMS\n- `{ \"status\": \"cancelled\" }` para cancelamento\n- `{ \"startsAt\": \"...\" }` para reagendar\n\nIdempotency-Key recomendado.","parameters":[{"name":"Idempotency-Key","in":"header","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["confirmed","attended","no_show","cancelled","rescheduled"]},"patientNotes":{"type":"string","maxLength":1000},"startsAt":{"type":"string","format":"date-time"}}}}}},"responses":{"200":{"description":"Marcação actualizada","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Appointment"}}}},"400":{"description":"Payload mal-formado","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"API key inválida ou em falta","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope insuficiente","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Recurso inexistente","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validação Zod falhou","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit excedido","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Segundos a esperar antes de retentar"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"delete":{"tags":["Appointments"],"summary":"Cancel appointment","description":"Equivalente a PATCH com `status: \"cancelled\"`.","responses":{"204":{"description":"Cancelada"},"400":{"description":"Payload mal-formado","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"API key inválida ou em falta","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope insuficiente","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Recurso inexistente","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validação Zod falhou","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit excedido","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Segundos a esperar antes de retentar"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/professionals":{"get":{"tags":["Professionals"],"summary":"List professionals","responses":{"200":{"description":"Lista de médicos","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Professional"}}}}}}},"400":{"description":"Payload mal-formado","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"API key inválida ou em falta","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope insuficiente","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Recurso inexistente","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validação Zod falhou","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit excedido","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Segundos a esperar antes de retentar"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/services":{"get":{"tags":["Services"],"summary":"List service types","responses":{"200":{"description":"Lista de tipos de consulta","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Service"}}}}}}},"400":{"description":"Payload mal-formado","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"API key inválida ou em falta","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope insuficiente","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Recurso inexistente","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validação Zod falhou","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit excedido","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Segundos a esperar antes de retentar"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/calls":{"get":{"tags":["Calls"],"summary":"List calls","description":"Listagem cursor-paginated, ordenada DESC por startedAt.","parameters":[{"name":"cursor","in":"query","schema":{"type":"string"}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"outcome","in":"query","schema":{"type":"string","enum":["booking_created","info_only","handoff_to_human","voicemail","abandoned"]}},{"name":"from","in":"query","schema":{"type":"string","format":"date-time"}},{"name":"to","in":"query","schema":{"type":"string","format":"date-time"}}],"responses":{"200":{"description":"Lista de chamadas","content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"$ref":"#/components/schemas/Call"}},"nextCursor":{"type":"string","nullable":true},"hasMore":{"type":"boolean"}}}}}},"400":{"description":"Payload mal-formado","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"API key inválida ou em falta","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope insuficiente","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Recurso inexistente","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validação Zod falhou","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit excedido","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Segundos a esperar antes de retentar"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/calls/{id}":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"tags":["Calls"],"summary":"Get call detail","description":"Detalhe da chamada + eventos (transcripts + tool calls + recording).","responses":{"200":{"description":"Detalhe da chamada","content":{"application/json":{"schema":{"type":"object","properties":{"call":{"allOf":[{"$ref":"#/components/schemas/Call"},{"type":"object","properties":{"metadata":{"type":"object","additionalProperties":true},"recordingUrl":{"type":"string","nullable":true},"recordingExpiresAt":{"type":"string","format":"date-time","nullable":true}}}]},"events":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"eventType":{"type":"string","enum":["speech","tool_call","tool_result","transcription_final"]},"sequence":{"type":"integer"},"transcript":{"type":"string","nullable":true},"toolName":{"type":"string","nullable":true},"toolInput":{"type":"object","additionalProperties":true,"nullable":true},"toolOutput":{"type":"object","additionalProperties":true,"nullable":true},"occurredAt":{"type":"string","format":"date-time"}}}}}}}}},"400":{"description":"Payload mal-formado","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"API key inválida ou em falta","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope insuficiente","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Recurso inexistente","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validação Zod falhou","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit excedido","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Segundos a esperar antes de retentar"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/v1/calls/{id}/recording":{"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"get":{"tags":["Calls"],"summary":"Get recording signed URL","description":"Devolve URL pré-assinado para o ficheiro audio em R2. Expira em 24h.","responses":{"200":{"description":"Signed URL","content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string","format":"uri"},"expiresInSeconds":{"type":"integer","example":86400}}}}}},"400":{"description":"Payload mal-formado","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"API key inválida ou em falta","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Scope insuficiente","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Chamada sem recording disponível (ou call não encontrada)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"description":"Validação Zod falhou","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit excedido","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Segundos a esperar antes de retentar"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"schemas":{"Tenant":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"clerkOrgId":{"type":"string"},"name":{"type":"string","example":"Clínica Sorriso"},"legalName":{"type":"string","nullable":true},"nif":{"type":"string","nullable":true,"example":"506332014"},"address":{"type":"string","nullable":true},"postalCode":{"type":"string","nullable":true},"city":{"type":"string","nullable":true,"example":"Lisboa"},"country":{"type":"string","example":"PT"},"email":{"type":"string","format":"email","nullable":true},"phone":{"type":"string","nullable":true},"specialty":{"type":"string","enum":["dental","medical","physio","aesthetic","other"]},"publicSlug":{"type":"string","nullable":true,"example":"clinica-sorriso"},"publicBookingEnabled":{"type":"boolean"},"plan":{"type":"string","enum":["basic","pro","clinica_plus","enterprise"]},"status":{"type":"string","enum":["trial","active","past_due","canceled"]},"trialEndsAt":{"type":"string","format":"date-time","nullable":true},"config":{"type":"object","description":"Tenant-specific config (voice AI, business hours metadata, etc.)","properties":{"voice":{"type":"string"},"voiceSpeed":{"type":"number"},"greeting":{"type":"string"},"timezone":{"type":"string"},"fallbackHumanNumber":{"type":"string","nullable":true}}},"referralCode":{"type":"string","nullable":true},"createdAt":{"type":"string","format":"date-time"},"updatedAt":{"type":"string","format":"date-time"}},"required":["id","name","specialty","status"]},"Appointment":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"professionalId":{"type":"string","format":"uuid"},"serviceTypeId":{"type":"string","format":"uuid","nullable":true},"patientName":{"type":"string","example":"Maria Silva"},"patientPhoneHash":{"type":"string","description":"HMAC-SHA256 hash do telefone (PII protegida)"},"startsAt":{"type":"string","format":"date-time","example":"2026-05-14T10:00:00+01:00"},"endsAt":{"type":"string","format":"date-time"},"status":{"type":"string","enum":["confirmed","attended","no_show","cancelled","rescheduled"]},"source":{"type":"string","enum":["voice_ai","manual","self_service","sync_external"]},"patientNotes":{"type":"string","nullable":true},"metadata":{"type":"object","additionalProperties":true,"nullable":true},"createdAt":{"type":"string","format":"date-time"}},"required":["id","professionalId","patientName","startsAt","endsAt","status","source"]},"Professional":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string","example":"Dr. João Marcos"},"title":{"type":"string","nullable":true,"example":"Dr."},"specialty":{"type":"string","example":"Medicina Dentária"},"bio":{"type":"string","nullable":true},"photoUrl":{"type":"string","format":"uri","nullable":true},"email":{"type":"string","format":"email","nullable":true},"metadata":{"type":"object","properties":{"calendarColor":{"type":"string","example":"#8B2635"}}}},"required":["id","name","specialty"]},"Service":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string","example":"Consulta de Avaliação"},"description":{"type":"string","nullable":true},"durationMinutes":{"type":"integer","example":30},"bufferMinutes":{"type":"integer","example":0},"priceEur":{"type":"string","nullable":true,"example":"50.00"},"eligibleProfessionalIds":{"type":"array","items":{"type":"string","format":"uuid"}},"active":{"type":"boolean"}},"required":["id","name","durationMinutes","active"]},"Call":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"telnyxCallControlId":{"type":"string","nullable":true},"fromNumber":{"type":"string","example":"+351912345678"},"toNumber":{"type":"string","example":"+351912004471"},"status":{"type":"string","example":"completed"},"outcome":{"type":"string","nullable":true,"enum":["booking_created","info_only","handoff_to_human","voicemail","abandoned",null]},"startedAt":{"type":"string","format":"date-time"},"endedAt":{"type":"string","format":"date-time","nullable":true},"durationSeconds":{"type":"integer","nullable":true},"transcriptionWordCount":{"type":"integer","nullable":true}},"required":["id","fromNumber","toNumber","status","startedAt"]},"Error":{"type":"object","properties":{"error":{"type":"string","example":"validation_failed"},"message":{"type":"string","example":"patientPhone must be E.164 format"},"requestId":{"type":"string","format":"uuid"},"errors":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"message":{"type":"string"}}}}},"required":["error","message"]}},"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"API key no header `Authorization: Bearer miv_live_...`. Gere keys em [Settings → Integrations](https://mivicall.com/settings/integrations)."}}}}