{"openapi":"3.0.3","info":{"title":"CARESUITE — API do Portal do Paciente","description":"API REST para o Portal do Paciente da CARESUITE.\n\n**Arquitetura multi-tenant:**\n\n- **Central** (`api.caresuite.com.br`): descoberta de clínicas via `/v1/tenants/*` (k-anonymity por prefixo de hash de CPF). Só armazena hashes — nunca CPFs crus.\n- **Por clínica** (`{dominio-da-clinica}/api/*`): auth + dados do paciente. Cada clínica hospeda sua própria API com sua base de dados.\n\n**Fluxo do app mobile:**\n\n1. App hasheia o CPF (SHA-256). Envia `prefix = hash[0..5]` para `GET {central}/v1/tenants/lookup`.\n2. Central devolve candidatos. App filtra localmente por hash exato.\n3. 0 → não cadastrado; 1 → direto; 2+ → seletor de clínica.\n4. App usa o `base_url` da clínica escolhida e chama `POST {clinic}/api/auth/login`.\n5. Token Bearer (JWT-like, 7 dias) é usado em todas as demais chamadas `{clinic}/api/patient/*`.","version":"1.1.6","contact":{"name":"CARESUITE","url":"https://caresuite.com.br"}},"servers":[{"url":"https://api.caresuite.com.br","description":"Central — descoberta multi-tenant (/v1/tenants/*)"},{"url":"https://{clinicDomain}","description":"Clínica — auth + dados do paciente (/api/*)","variables":{"clinicDomain":{"default":"clinicax.caresuite.com.br"}}}],"tags":[{"name":"Descoberta","description":"Diretório multi-tenant de clínicas (k-anonymity via prefixo de hash)."},{"name":"Autenticação","description":"Login, sessão e dados do usuário logado."},{"name":"Paciente","description":"Dados pessoais, agendamentos, exames, documentos e financeiro."}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"},"tenantKey":{"type":"apiKey","in":"header","name":"X-Tenant-Key"}},"schemas":{"ApiError":{"type":"object","properties":{"success":{"type":"boolean","example":false},"message":{"type":"string"},"data":{"type":["object","null"]}}},"ApiSuccess":{"type":"object","properties":{"success":{"type":"boolean","example":true},"message":{"type":"string"},"data":{"type":["object","null"]}}},"LoginResponse":{"allOf":[{"$ref":"#/components/schemas/ApiSuccess"},{"type":"object","properties":{"data":{"type":"object","properties":{"token":{"type":"string","example":"eyJhbGciOi..."},"token_type":{"type":"string","example":"Bearer"},"expires_in":{"type":"integer","example":604800},"expires_at":{"type":"string","format":"date-time"},"user":{"$ref":"#/components/schemas/User"},"patient":{"$ref":"#/components/schemas/Patient"}}}}}]},"User":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"email":{"type":["string","null"]},"cpf":{"type":"string"},"type":{"type":"string","example":"paciente"}}},"Patient":{"type":"object","properties":{"id":{"type":"integer"},"user_id":{"type":["integer","null"]},"full_name":{"type":"string"},"social_name":{"type":["string","null"]},"cpf":{"type":"string"},"rg":{"type":["string","null"]},"birth_date":{"type":["string","null"],"format":"date"},"gender":{"type":["string","null"],"enum":["M","F","O",null]},"mother_name":{"type":["string","null"]},"phone":{"type":["string","null"]},"whatsapp":{"type":["string","null"]},"email":{"type":["string","null"],"format":"email"},"avatar":{"type":["string","null"]},"zip_code":{"type":["string","null"]},"street":{"type":["string","null"]},"number":{"type":["string","null"]},"complement":{"type":["string","null"]},"neighborhood":{"type":["string","null"]},"city":{"type":["string","null"]},"state":{"type":["string","null"]},"country":{"type":"string","example":"BR"},"insurance_name":{"type":["string","null"]},"insurance_plan_name":{"type":["string","null"]},"insurance_card":{"type":["string","null"]},"emergency_contact_name":{"type":["string","null"]},"emergency_contact_phone":{"type":["string","null"]},"status":{"type":"string","enum":["active","inactive"]},"created_at":{"type":"string","format":"date-time"},"updated_at":{"type":"string","format":"date-time"}}},"Appointment":{"type":"object","properties":{"id":{"type":"integer"},"scheduled_at":{"type":"string","format":"date-time"},"duration_minutes":{"type":["integer","null"]},"status":{"type":"string"},"type":{"type":["string","null"]},"price":{"type":["number","null"]},"notes":{"type":["string","null"]},"doctor_id":{"type":"integer"},"doctor_name":{"type":"string"},"specialty_name":{"type":["string","null"]},"unit_name":{"type":["string","null"]},"room_name":{"type":["string","null"]}}},"Exam":{"type":"object","properties":{"id":{"type":"integer"},"description":{"type":["string","null"]},"exam_date":{"type":["string","null"],"format":"date"},"file_name":{"type":"string"},"original_name":{"type":"string"},"file_path":{"type":"string"},"mime_type":{"type":["string","null"]},"size":{"type":["integer","null"]},"created_at":{"type":"string","format":"date-time"}}},"Document":{"type":"object","properties":{"id":{"type":"integer"},"type":{"type":"string","enum":["atestado","receita","solicitacao_exame","declaracao","encaminhamento","relatorio","outro"]},"title":{"type":"string"},"issued_at":{"type":"string","format":"date-time"},"doctor_id":{"type":["integer","null"]},"doctor_name":{"type":["string","null"]}}},"Receivable":{"type":"object","properties":{"id":{"type":"integer"},"description":{"type":"string"},"amount":{"type":"number"},"paid_amount":{"type":["number","null"]},"due_date":{"type":"string","format":"date"},"paid_at":{"type":["string","null"],"format":"date-time"},"status":{"type":"string","enum":["pendente","parcial","pago","vencido","cancelado"]},"payment_method":{"type":["string","null"]},"appointment_id":{"type":["integer","null"]}}},"TenantMatch":{"type":"object","properties":{"slug":{"type":"string","example":"clinica-x"},"name":{"type":"string","example":"Clínica X"},"base_url":{"type":"string","format":"uri","example":"https://clinicax.caresuite.com.br"},"logo_url":{"type":["string","null"],"format":"uri"},"city":{"type":["string","null"]},"state":{"type":["string","null"]},"cpf_hash":{"type":"string","description":"Hash completo — app compara localmente."}}},"MedicalRecord":{"type":["object","null"],"properties":{"summary":{"type":["string","null"]},"blood_type":{"type":["string","null"]},"allergies":{"type":["string","null"]},"chronic_conditions":{"type":["string","null"]},"data":{"type":["object","null"]},"updated_at":{"type":"string","format":"date-time"}}}},"responses":{"Unauthorized":{"description":"Token ausente ou inválido.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"NotFound":{"description":"Recurso não encontrado.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}},"security":[{"bearerAuth":[]}],"paths":{"/v1/tenants/lookup":{"get":{"tags":["Descoberta"],"summary":"Lookup k-anonymous de clínicas por prefixo de hash de CPF","description":"Fluxo de privacidade:\n\n1. App calcula `hash = sha256(cpf_digits)` em hex lowercase.\n2. App envia **apenas** os primeiros 5 chars como `prefix`.\n3. Servidor devolve todos os hashes daquele bucket + clínicas associadas.\n4. App filtra localmente pelo hash completo.\n\nO servidor nunca confirma um CPF específico — só um bucket. ~1M buckets (16^5).","security":[],"parameters":[{"name":"prefix","in":"query","required":true,"schema":{"type":"string","pattern":"^[a-f0-9]{5}$"},"example":"3f4a9"}],"responses":{"200":{"description":"Lista de clínicas e hashes dentro do bucket.","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"message":{"type":["string","null"]},"data":{"type":"object","properties":{"prefix":{"type":"string"},"count":{"type":"integer"},"matches":{"type":"array","items":{"$ref":"#/components/schemas/TenantMatch"}}}}}}}}},"400":{"$ref":"#/components/responses/Unauthorized"}}}},"/v1/tenants/patients":{"post":{"tags":["Descoberta"],"summary":"Registra (upsert) um hash de paciente da clínica","description":"Chamado por cada instalação da clínica ao criar/atualizar paciente. Autenticação via header `X-Tenant-Key`.","security":[{"tenantKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["cpf_hash"],"properties":{"cpf_hash":{"type":"string","pattern":"^[a-f0-9]{64}$","description":"SHA-256 do CPF (somente dígitos) em hex lowercase."}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiSuccess"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}},"delete":{"tags":["Descoberta"],"summary":"Remove um hash de paciente da clínica","security":[{"tenantKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["cpf_hash"],"properties":{"cpf_hash":{"type":"string","pattern":"^[a-f0-9]{64}$"}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiSuccess"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/auth/login":{"post":{"tags":["Autenticação"],"summary":"Login com CPF e senha","description":"Autentica um paciente pelo CPF e senha numérica (até 8 dígitos). Retorna Bearer Token e todos os dados do paciente.","security":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["cpf","password"],"properties":{"cpf":{"type":"string","example":"123.456.789-09","description":"CPF com ou sem máscara."},"password":{"type":"string","example":"12345678","description":"Senha numérica (até 8 dígitos)."}}}}}},"responses":{"200":{"description":"Login realizado com sucesso.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/auth/me":{"get":{"tags":["Autenticação"],"summary":"Retorna dados do paciente autenticado","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"message":{"type":["string","null"]},"data":{"user":{"$ref":"#/components/schemas/User"},"patient":{"$ref":"#/components/schemas/Patient"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/auth/logout":{"post":{"tags":["Autenticação"],"summary":"Encerra a sessão (stateless)","description":"Como o token é stateless (JWT-like), o servidor apenas reconhece o pedido. O cliente deve descartar o token localmente.","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiSuccess"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/patient/profile":{"get":{"tags":["Paciente"],"summary":"Perfil completo do paciente","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"message":{"type":["string","null"]},"data":{"$ref":"#/components/schemas/Patient"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}},"put":{"tags":["Paciente"],"summary":"Atualiza dados de contato/endereço","requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"phone":{"type":"string"},"whatsapp":{"type":"string"},"email":{"type":"string","format":"email"},"zip_code":{"type":"string"},"street":{"type":"string"},"number":{"type":"string"},"complement":{"type":"string"},"neighborhood":{"type":"string"},"city":{"type":"string"},"state":{"type":"string","maxLength":2},"emergency_contact_name":{"type":"string"},"emergency_contact_phone":{"type":"string"}}}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiSuccess"}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/patient/dashboard":{"get":{"tags":["Paciente"],"summary":"Painel resumido (próximas consultas + contadores)","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"message":{"type":["string","null"]},"data":{"patient":{"$ref":"#/components/schemas/Patient"},"upcoming":{"type":"array","items":{"$ref":"#/components/schemas/Appointment"}},"totals":{"type":"object","properties":{"finalized":{"type":"integer"},"exams":{"type":"integer"},"documents":{"type":"integer"},"pending_bills":{"type":"integer"}}}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/patient/appointments":{"get":{"tags":["Paciente"],"summary":"Lista agendamentos do paciente","parameters":[{"name":"status","in":"query","schema":{"type":"string","enum":["agendado","confirmado","aguardando","chamado","em_atendimento","finalizado","faltou","cancelado"]}},{"name":"from","in":"query","schema":{"type":"string","format":"date"}},{"name":"to","in":"query","schema":{"type":"string","format":"date"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"message":{"type":["string","null"]},"data":{"type":"array","items":{"$ref":"#/components/schemas/Appointment"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/patient/appointments/{id}":{"get":{"tags":["Paciente"],"summary":"Detalhe de um agendamento","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"message":{"type":["string","null"]},"data":{"$ref":"#/components/schemas/Appointment"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/api/patient/exams":{"get":{"tags":["Paciente"],"summary":"Exames do paciente (arquivos)","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"message":{"type":["string","null"]},"data":{"type":"array","items":{"$ref":"#/components/schemas/Exam"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/patient/documents":{"get":{"tags":["Paciente"],"summary":"Documentos emitidos para o paciente","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"message":{"type":["string","null"]},"data":{"type":"array","items":{"$ref":"#/components/schemas/Document"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/patient/receivables":{"get":{"tags":["Paciente"],"summary":"Contas a receber (financeiro do paciente)","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"message":{"type":["string","null"]},"data":{"type":"array","items":{"$ref":"#/components/schemas/Receivable"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}},"/api/patient/medical-record":{"get":{"tags":["Paciente"],"summary":"Prontuário resumo (alergias, tipagem sanguínea, etc.)","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean","example":true},"message":{"type":["string","null"]},"data":{"$ref":"#/components/schemas/MedicalRecord"}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"}}}}}}