v1server-to-serverJSON
API Externa da Alana
Interface HTTP autenticada por API key para integrar sistemas parceiros à Alana. Cada chave pertence a uma clínica — você nunca envia o id da clínica, a chave já resolve o tenant.
Visão geral
- Todas as respostas são JSON. Envie
Content-Type: application/jsonnos corpos. - Datas de data-e-hora são ISO 8601 em UTC (ex.:
2026-09-10T13:00:00.000Z). Datas só-dia sãoAAAA-MM-DD. - Campos no corpo e na resposta usam
snake_case. - Ids são strings opacas. Telefones são só dígitos, com DDI e DDD (ex.:
5511999999999). - Não há CORS: chame a partir do seu backend, nunca do navegador do usuário final.
Quick start
1. Crie uma chave
No painel da Alana, em Personalizar Alana → API Externa, clique em Criar chave, nomeie e selecione apenas os escopos necessários. O segredo aparece uma única vez — copie e guarde no cofre de credenciais.
2. Confirme a credencial
curl -s BASE/me \
-H "Authorization: Bearer SUA_CHAVE"
Resposta traz o nome, o timezone e o expediente da clínica, além dos escopos da chave.
3. Faça a primeira chamada útil
curl -s "BASE/contacts?limit=5" \
-H "Authorization: Bearer SUA_CHAVE"
Autenticação
Toda requisição precisa do header:
Authorization: Bearer alk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Sem o header, ou com uma chave inválida/revogada, a resposta é 401 unauthorized. Se a chave é válida mas não tem o escopo necessário, é 403 forbidden.
Escopos
Cada chave carrega uma lista de escopos. Peça só o que a integração usa.
| Escopo | Permite |
|---|---|
| identity.read | GET /me |
| contacts.read | Listar e ler contatos |
| contacts.write | Criar e editar contatos e suas etiquetas |
| crm.read | Ler etapas do funil e o board |
| crm.write | Mover um contato de etapa |
| catalog.read | Ler procedimentos e produtos |
| professionals.read | Ler profissionais |
| agenda.read | Ler agenda e disponibilidade |
| agenda.write | Criar e alterar agendamentos |
Rate limit
Por chave: 60 requisições por minuto e 1000 por hora. Ao estourar, a resposta é 429 rate_limited. Os headers RateLimit-* acompanham a janela de minuto.
Idempotência
Em POST e PATCH, envie um header Idempotency-Key com um valor único por operação (ex.: um UUID). Se a mesma chave chegar de novo, a Alana devolve a mesma resposta da primeira vez, com o header Idempotency-Replayed: true, sem reexecutar a ação. Use isso para retries seguros.
curl -X POST BASE/appointments \
-H "Authorization: Bearer SUA_CHAVE" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 5f0b1e6a-..." \
-d '{ "contact": { "phone": "5511999999999" }, "procedure_id": "...", "start": "2026-09-10T13:00:00Z" }'
Erros
O corpo de erro é sempre:
{ "error": { "code": "invalid_request", "message": "phone é obrigatório..." } }
| HTTP | code | Quando |
|---|---|---|
| 400 / 422 | invalid_request | Corpo ou parâmetro inválido |
| 401 | unauthorized | Chave ausente, inválida ou revogada |
| 403 | forbidden | A chave não tem o escopo |
| 404 | not_found | Recurso ou endpoint inexistente |
| 409 | conflict | Ex.: horário já ocupado |
| 429 | rate_limited | Limite de requisições |
| 500 | internal | Erro interno — pode repetir |
Paginação
Listas de contatos usam cursor: a resposta traz { "data": [...], "next_cursor": "..." }. Passe ?cursor= com esse valor para a próxima página; next_cursor: null significa fim. ?limit= vai de 1 a 200 (padrão 50).
Identidade
Escopo identity.read. Valida a chave e devolve os dados da clínica.
{
"clinic": { "id": "...", "name": "Diamond Clinic", "timezone": "America/Sao_Paulo",
"work_days": "1,2,3,4,5", "work_start_hour": 9, "work_end_hour": 19 },
"key": { "name": "Integração site", "scopes": ["identity.read", "contacts.read"] }
}
Contatos
Escopo contacts.read.
| Query | Descrição |
|---|---|
search | Filtra por nome ou telefone |
limit | 1–200 (padrão 50) |
cursor | Da página anterior |
Escopo contacts.read. Objeto do contato:
{
"id": "...", "name": "Odete", "phone": "5514999...", "email": null, "cpf": null,
"notes": null, "birth_date": "1980-04-12", "funnel_stage": "novo_lead",
"tags": [{ "id": "...", "label": "Indicação", "color": "#16a34a" }],
"created_at": "2026-08-30T19:25:00.000Z"
}
Escopo contacts.write. Cria ou atualiza pelo telefone (upsert).
| Campo | |
|---|---|
phone * | Só dígitos, DDI+DDD |
name, email, cpf, notes | Opcionais |
birth_date | AAAA-MM-DD |
tags | Array de rótulos (texto). Cria a etiqueta se não existir e substitui as do contato. |
Escopo contacts.write. Atualiza só os campos enviados.
CRM
Escopo crm.read. Etapas do funil: { id, label, color, kind, order }. kind ∈ aberta | avaliacao_agendada | ganho | pos_procedimento | perdido.
Escopo crm.read. Etapas com a lista de contatos em cada uma.
Escopo crm.write. Corpo { "stage": "interesse_confirmado" } (um id de /crm/stages).
Catálogo
Escopo catalog.read. { id, name, duration_min, price, price_variable, payment_methods, description }.
Escopo catalog.read. { id, name, price, description }.
Profissionais
Escopo professionals.read. { id, name, bio, instagram, procedures: [{ id, name }] }.
Agenda
Escopo agenda.read. Query: from, to (ISO; padrão -7d a +30d), status.
{
"data": [{
"id": "...", "start": "2026-09-10T13:00:00.000Z", "start_local": "quinta-feira, 10/09 às 10:00",
"status": "confirmed", "patient_confirmed": false,
"procedure": { "id": "...", "name": "Toxina botulínica" },
"professional": { "id": "...", "name": "Dra. Natieli" },
"contact": { "id": "...", "name": "Odete", "phone": "5514999..." }
}]
}
Escopo agenda.read. Query: procedure_id * , professional_id (opcional), days (1–30, padrão 10). Retorna horários livres já considerando expediente, duração, conflitos, bloqueios e a agenda de cada profissional.
{ "data": [{ "start": "2026-09-10T13:00:00.000Z", "start_local": "quinta-feira, 10/09 às 10:00",
"professional": { "id": "...", "name": "Dra. Natieli" } }] }
Escopo agenda.write. Revalida o horário dentro de uma transação — se acabou de ser ocupado, responde 409 conflict.
{
"contact": { "phone": "5511999999999", "name": "Odete" },
"procedure_id": "...", "professional_id": null,
"start": "2026-09-10T13:00:00Z"
}
Escopo agenda.write. Corpo com status (confirmed|completed|cancelled), start e/ou professional_id. Ao cancelar, a Alana oferece a vaga liberada para a lista de espera.
Checklist do parceiro
- Crie a chave com os escopos mínimos e guarde o segredo no cofre.
- Valide com
GET /mee leia otimezoneda clínica. - Converta horários para UTC antes de enviar (
startsempre em ISO/UTC). - Use
Idempotency-Keyem toda escrita e trate409/429com retry. - Paginе contatos por
next_cursor. - Nunca chame a API do navegador do cliente final — só do seu backend.
- Ao desativar a integração, revogue a chave no painel.