Verandi

API da Verandi

A Verandi expõe a agenda de um estúdio ou clínica para outros sistemas: consultar horários com vaga, cadastrar quem é atendido, marcar e desmarcar. Esta página é tudo que existe, e dá para fazer a primeira chamada em dois minutos.

https://verandi.4yu.com.br/api/v1

Começando

1. Pegue a chave

Na Verandi, entre em Configuração, Integrações, e clique em Ligar. A chave aparece uma vez e não volta a aparecer: guarde na hora. Se perder, revogue e crie outra.

2. Faça a primeira chamada

Se voltar o catálogo do negócio, está tudo certo, e você já tem os identificadores para usar no resto.

curl https://verandi.4yu.com.br/api/v1/catalogo \
  -H "Authorization: Bearer vr_sua_chave_aqui"

3. Trate o 401

Chave ausente, errada, revogada ou de conta desligada dão a mesma resposta 401. É de propósito: distinguir uma da outra contaria a quem está tentando qual das portas já existiu.

Cinco coisas que evitam retrabalho

Datas são sempre locais do negócio

Data é AAAA-MM-DD e hora é HH:MM, no fuso da conta. A API recusa instante em UTC, e essa recusa é um favor: a turma das 21h em Brasília é 00h do dia seguinte em UTC, e aceitar os dois formatos é como nasce a aula marcada no dia errado.

Repita à vontade, com Idempotency-Key

Toda escrita aceita o cabeçalho Idempotency-Key, com um valor que você escolhe. Se a mesma chamada chegar duas vezes com a mesma chave, a segunda recebe a mesma resposta e nada acontece duas vezes. Use um identificador da sua conversa ou do seu evento. Mesma chave com conteúdo diferente é recusada com 422, porque isso não é reentrega, é engano.

-H "Idempotency-Key: conversa-8f21a"

O que a integração não faz

Não cria nem altera grade, serviço, profissional, local ou capacidade: isso é configuração, e configuração é da tela. Não apaga nada. Não lê nem escreve observação, que é onde o negócio anota informação de saúde. E não marca ninguém em horário cheio, mesmo quando a conta permite encaixe acima da lotação para quem está no balcão.

Erros

Todo erro tem a mesma forma, e quando o problema é um campo específico ele vem nomeado. 400 é pedido malformado; 401 é chave; 404 é recurso que não é desta conta; 409 é regra de negócio, como horário cheio ou pessoa já marcada; 422 é a mesma Idempotency-Key com outro conteúdo; 500 é nosso.

{ "erro": "o intervalo não pode passar de 90 dias", "campo": "ate" }

Versão

O caminho começa com /v1. Dentro de uma versão, campo novo pode aparecer sem aviso, e campo existente não muda de significado nem some. Escreva seu cliente ignorando o que não conhece.

Rotas

GET/disponibilidade

Horários com vaga

Os horários de um intervalo, separados em livres e cheios. É a rota que responde "tem aula quinta de manhã?".

Ofereça apenas o que vier em livres. Horário cheio nunca entra nessa lista, nem quando falta pouco: a decisão de encaixar alguém acima da lotação é de quem está no balcão, olhando para a pessoa. cheios existe para o seu sistema saber a diferença entre "não há horário nesse dia" e "há, e está lotado", que são duas conversas diferentes. Para quem ainda não é aluno, use experimental=1: a aula experimental tem grade própria, mais estreita que a agenda, e oferecer fora dela é prometer o que o estúdio não dá.

Parâmetros

deobrigatóriodataprimeiro dia, AAAA-MM-DD
ateobrigatóriodataúltimo dia, no máximo 90 dias depois de de
servicoidfiltra por serviço
profissionalidfiltra por quem atende
localidfiltra por sala
experimentaltextouse 1 para ver só o que o estúdio aceita dar como aula experimental. A grade dela é própria, mais estreita que a agenda de quem já é aluno

Exemplo

curl "https://verandi.4yu.com.br/api/v1/disponibilidade?de=2026-08-17&ate=2026-08-23" \
  -H "Authorization: Bearer vr_sua_chave_aqui"

Resposta

{
  "de": "2026-08-17",
  "ate": "2026-08-23",
  "livres": [
    {
      "sessaoId": "a41f...",
      "data": "2026-08-18", "hora": "07:00", "duracaoMin": 60,
      "servico": "Pilates solo",
      "profissionalId": "2b7e...", "profissional": "Marina",
      "localId": "6d33...", "local": "Sala 1",
      "capacidade": 4, "ocupadas": 2, "livres": 2
    }
  ],
  "cheios": []
}
GET/pessoas

Procurar uma pessoa

Por telefone, para reconhecer quem chegou; ou por nome, sem acento e sem diferenciar maiúscula. Chame sempre antes de cadastrar, senão a mesma pessoa vira três cadastros porque escreveu o nome de três jeitos.

Pelo telefone é como um robô reconhece quem está falando: quem escreve "oi" não disse nome nenhum. Procuramos por todas as formas do mesmo número (com e sem o país, com e sem o nono dígito), porque contas antigas do WhatsApp vêm sem ele. Número sem DDD não é procurado: ele pode ser de onze estados, e reconhecer a pessoa errada não tem conserto. Não achar responde 200 com lista vazia, e não 404: no cadastro real 30% não têm telefone, então não reconhecer é o caminho normal desta rota. Telefone pode ser da família (mãe e filha no mesmo número): aí vem mais de uma, a ativa mais nova primeiro, e quem conversa pergunta o nome. O telefone é procurado nos três campos da ficha (principal, residencial e comercial), então o segundo número da família também reconhece.

Parâmetros

telefonetextocom DDD; aceita máscara e o país
emailtextoo e-mail inteiro, sem diferenciar maiúscula; pode devolver mais de uma pessoa
buscatextono mínimo duas letras; use quando não tiver o número

Exemplo

curl "https://verandi.4yu.com.br/api/v1/pessoas?telefone=5544998887766" \
  -H "Authorization: Bearer vr_sua_chave_aqui"

Resposta

{
  "total": 1,
  "pessoas": [
    { "pessoaId": "77c0...", "nome": "Marina Alves", "telefone": "11988887777", "ativa": true }
  ]
}
POST/pessoas

Cadastrar uma pessoa

Cadastra quem a busca não achou. Nome é o único campo obrigatório.

A rota não recusa nomes parecidos. "Ana" e "Ana Paula" podem ser a mesma pessoa ou duas, e quem sabe é a conversa, não o banco. Procure antes.

Corpo

nomeobrigatóriotextoaté 120 caracteres
telefonetextocomo o negócio quiser guardar, até 40 caracteres
identificadorExternotextoo código dessa pessoa no seu sistema, até 60 caracteres

Exemplo

curl -X POST https://verandi.4yu.com.br/api/v1/pessoas \
  -H "Authorization: Bearer vr_sua_chave_aqui" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: conversa-8f21a" \
  -d '{ "nome": "Marina Alves", "telefone": "11988887777" }'

Resposta

201 Created

{
  "pessoaId": "77c0...",
  "nome": "Marina Alves",
  "telefone": "11988887777",
  "ativa": true
}
GET/pessoas/{pessoaId}

A agenda de uma pessoa

Os horários fixos dela, o que vem pela frente e quantas reposições estão em aberto. É a rota que responde "quais são meus horários?" e "quantas aulas eu tenho para repor?".

É daqui que sai o participacaoId, e sem ele não há como desmarcar. Guarde o identificador quando marcar, ou consulte esta rota antes de cancelar. Cada item de proximas diz em podeReporSeCancelarAgora se cancelar neste momento ainda dá direito à reposição: consulte antes de confirmar o cancelamento, porque depois do DELETE a resposta chega tarde. O veredito envelhece, ele vale para o instante da consulta. Observação e data de nascimento nunca aparecem aqui: são dados de ficha, e ficha é da tela. modalidadeUnica traz servicoId e nome quando os contratos ativos dela são de uma modalidade só, e null quando são mais de uma ou nenhuma: com ela o bot não precisa perguntar qual aula.

Exemplo

curl https://verandi.4yu.com.br/api/v1/pessoas/77c0... \
  -H "Authorization: Bearer vr_sua_chave_aqui"

Resposta

{
  "pessoaId": "77c0...",
  "nome": "Marina Alves",
  "telefone": "11988887777",
  "ativa": true,
  "situacao": "ativa",
  "ultimaPresenca": "2026-08-11",
  "horariosFixos": [
    { "vagaId": "13aa...", "diaSemana": 2, "hora": "07:00", "servico": "Pilates solo",
      "profissional": "Marina", "desde": "2026-03-02", "ate": null }
  ],
  "proximas": [
    { "participacaoId": "5e90...", "sessaoId": "a41f...", "data": "2026-08-18",
      "hora": "07:00", "inicio": "2026-08-18T10:00:00Z", "servico": "Pilates solo",
      "origem": "recorrente", "status": "esperada",
      "podeReporSeCancelarAgora": true, "minutosAteAula": 1440 }
  ],
  "reposicoesAbertas": [
    { "participacaoId": "1c44...", "data": "2026-08-04", "hora": "07:00",
      "servico": "Pilates solo", "motivo": "falta_avisada" }
  ],
  "regraDeCancelamento": { "minutosMinimos": 120, "porExtenso": "2h" }
}
POST/participacoes

Marcar em um horário

Coloca uma pessoa em um horário. O sessaoId vem da rota de disponibilidade.

A vaga é conferida no momento de gravar, e não no momento em que você leu a disponibilidade. Entre uma coisa e outra alguém pode ter ocupado, e nesse caso a resposta é 409. Trate o 409 como resposta normal do fluxo, não como falha.

Corpo

pessoaIdobrigatórioidquem vai
sessaoIdobrigatórioidqual horário
origemtextoavulso (padrão), reposicao, encaixe ou reserva
reposicaoDeIdidobrigatório quando origem é reposicao: qual falta está sendo reposta

Exemplo

curl -X POST https://verandi.4yu.com.br/api/v1/participacoes \
  -H "Authorization: Bearer vr_sua_chave_aqui" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: conversa-8f21b" \
  -d '{ "pessoaId": "77c0...", "sessaoId": "a41f..." }'

Resposta

201 Created

{
  "participacaoId": "5e90...",
  "pessoaId": "77c0...",
  "sessaoId": "a41f...",
  "origem": "avulso",
  "status": "esperada"
}
GET/participacoes/{participacaoId}

Uma marcação, e se dá tempo de cancelar

Diz o dia, a hora e o serviço de uma marcação, e responde a pergunta que vem antes de cancelar: se desmarcar agora, a pessoa ainda ganha a reposição?

Consulte esta rota depois que a pessoa escolher a aula e antes de chamar o DELETE. O campo avisoParaConfirmar já vem com a frase pronta para mostrar quando estiver fora do prazo, e vem null quando não há nada a avisar. O veredito vale para o instante da consulta e envelhece: quem grava a decisão é o DELETE, que refaz a conta na hora do aviso.

Exemplo

curl https://verandi.4yu.com.br/api/v1/participacoes/5e90... \
  -H "Authorization: Bearer vr_sua_chave_aqui"

Resposta

{
  "participacaoId": "5e90...",
  "pessoaId": "77c0...",
  "nome": "Marina Alves",
  "sessaoId": "a41f...",
  "data": "2026-08-18",
  "hora": "07:00",
  "inicio": "2026-08-18T10:00:00Z",
  "servico": "Pilates solo",
  "servicoId": "b2d1...",
  "origem": "recorrente",
  "status": "esperada",
  "jaPassou": false,
  "podeCancelar": true,
  "podeReporSeCancelarAgora": false,
  "minutosAteAula": 45,
  "regraDeCancelamento": { "minutosMinimos": 120, "porExtenso": "2h" },
  "avisoParaConfirmar": "Você está tentando cancelar a aula de 18/08 às 07:00 fora do prazo de 2h. Se confirmar, essa aula não poderá ser reposta. Deseja prosseguir?"
}
DELETE/participacoes/{participacaoId}

Desmarcar

Registra a falta justificada (a pessoa avisou que não vem). A vaga volta a ser oferecida na mesma hora, e a pessoa ganha o crédito de reposição se a conta trabalhar assim.

Apesar do verbo, nada é apagado: a marcação fica no histórico com o estado de falta justificada (`falta_avisada`). É isso que preserva o crédito de reposição e a contagem do negócio. Só funciona para horário futuro; aula que já aconteceu tem a chamada feita por quem estava na sala. Quem avisa com menos antecedência do que a conta exige desmarca do mesmo jeito, mas fica com status falta e temCredito false: a vaga abre e a reposição não. Chamar de novo devolve 200 com jaEstavaAssim true, para a reentrega não virar erro.

Exemplo

curl -X DELETE https://verandi.4yu.com.br/api/v1/participacoes/5e90... \
  -H "Authorization: Bearer vr_sua_chave_aqui"

Resposta

{
  "participacaoId": "5e90...",
  "status": "falta_avisada",
  "jaEstavaAssim": false,
  "temCredito": true,
  "minutosDeAntecedencia": 1440,
  "minutosExigidos": 120
}
POST/espera

Entrar na fila de um horário cheio

Quando a disponibilidade devolve o horário em cheios, esta rota transforma o não em "te aviso se abrir". Quando alguém desmarca, sai o evento vaga.aberta com quem está na frente.

Entrar na fila não reserva nada. Quando a vaga abre, a pessoa é chamada e precisa marcar como qualquer um. Reservar sozinho seria a integração decidindo, e criaria a pior conversa possível: "você foi marcada numa aula que não pediu".

Corpo

pessoaIdobrigatórioidquem quer ser avisado
sessaoIdobrigatórioidqual horário, o mesmo id que vem em cheios

Exemplo

curl -X POST https://verandi.4yu.com.br/api/v1/espera \
  -H "Authorization: Bearer vr_sua_chave_aqui" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: conversa-8f21c" \
  -d '{ "pessoaId": "77c0...", "sessaoId": "a41f..." }'

Resposta

201 Created

{
  "esperaId": "4d10...",
  "pessoaId": "77c0...",
  "sessaoId": "a41f...",
  "posicao": 2
}
DELETE/espera/{esperaId}

Sair da fila

A pessoa entrou na espera de terça, conseguiu marcar na quinta, e não quer mais ser avisada. Sem isto, o estúdio oferece uma vaga que ela já não precisa.

Exemplo

curl -X DELETE https://verandi.4yu.com.br/api/v1/espera/4d10... \
  -H "Authorization: Bearer vr_sua_chave_aqui"

Resposta

{
  "esperaId": "4d10...",
  "jaEstavaAssim": false
}
POST/licencas

Avisar que voltou de licença

A pessoa diz no WhatsApp que voltou. Com reagendou true (ela marcou aula na conversa) a licença fecha. Com reagendou false a licença continua aberta e nada mais muda: quem segue a conversa é a equipe, no atendimento.

Quem não tem licença aberta recebe 200 com tinhaLicenca false, e não erro: a pessoa diz que voltou mesmo quando ninguém registrou a saída. A ficha (GET /pessoas/{pessoaId}) traz licenca com inicio e voltaPrevista, ou null.

Corpo

pessoaIdobrigatórioidquem voltou
reagendouobrigatóriobooleanotrue se marcou aula na conversa; false se não quis marcar agora

Exemplo

curl -X POST https://verandi.4yu.com.br/api/v1/licencas \
  -H "Authorization: Bearer vr_sua_chave_aqui" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: conversa-8f21c" \
  -d '{ "pessoaId": "77c0...", "reagendou": true }'

Resposta

{
  "pessoaId": "77c0...",
  "tinhaLicenca": true,
  "licencaAberta": false
}
GET/relatorio

Resumo do período

Presença, falta, reposição, aula experimental que virou matrícula, matrícula nova e quem saiu, num período de até 400 dias. Só números, nenhum nome: é para relatório, não para lista.

Presença conta só aula que já aconteceu e não foi cancelada pela casa; licença fica fora da taxa. Matrícula nova é o primeiro contrato da pessoa, e sair é encerrar o contrato sem ter outro ativo ou pausado hoje: trocar de plano não conta como saída.

Parâmetros

deobrigatóriodataprimeiro dia, AAAA-MM-DD, no fuso da conta
ateobrigatóriodataúltimo dia, incluído

Exemplo

curl "https://verandi.4yu.com.br/api/v1/relatorio?de=2026-10-01&ate=2026-10-31" \
  -H "Authorization: Bearer vr_sua_chave_aqui"

Resposta

{
  "de": "2026-10-01",
  "ate": "2026-10-31",
  "presencas": 412,
  "faltas": 38,
  "faltasAvisadas": 51,
  "taxaDePresenca": 0.82,
  "reposicoesFeitas": 44,
  "experimentais": 12,
  "experimentaisQueMatricularam": 7,
  "matriculasNovas": 9,
  "sairam": 3,
  "alunosAtivos": 118
}
PUT/webhook

Ligar os avisos no AutoFluxos

O AutoFluxos se registra como destino dos avisos da conta, num clique do lado de lá. Daí em diante cada evento chega assinado com o segredo que ele mandou.

Pela API só um endereço do AutoFluxos é aceito: com uma chave vazada não dá para desviar os avisos, que levam nome e telefone, para outro lugar. Outro destino se configura na tela, pelo dono.

Corpo

urlobrigatóriotextoo endereço de recebimento do AutoFluxos para esta conta
segredoobrigatóriotextode 32 a 200 caracteres; assina cada entrega

Exemplo

curl -X PUT https://verandi.4yu.com.br/api/v1/webhook \
  -H "Authorization: Bearer vr_sua_chave_aqui" \
  -H "Content-Type: application/json" \
  -d '{ "url": "https://autofluxos.4yu.com.br/api/webhook/verandi/…", "segredo": "…" }'

Resposta

{
  "ativo": true,
  "url": "https://autofluxos.4yu.com.br/api/webhook/verandi/…"
}
DELETE/webhook

Desligar os avisos no AutoFluxos

Para de mandar os avisos para o AutoFluxos. Desliga, não apaga: o histórico de entregas continua.

Exemplo

curl -X DELETE https://verandi.4yu.com.br/api/v1/webhook \
  -H "Authorization: Bearer vr_sua_chave_aqui"

Resposta

{
  "ativo": false,
  "desligou": true
}

Receber avisos da Verandi

As rotas acima são você perguntando. Esta parte é o contrário: a recepção cancela a aula de quinta na tela, e o seu sistema precisa saber para avisar quem ia.

Ligue o aviso

Na Verandi, em Configuração, Integrações, informe o endereço https do seu sistema. O segredo de assinatura aparece uma vez, na hora. Trocar o endereço gera um segredo novo, e o anterior para de valer.

O que chega

Um POST com o corpo abaixo. Cinco eventos hoje: participacao.criada, participacao.cancelada, sessao.cancelada, vaga.aberta, quando abre vaga em horário que tinha fila, e presenca.falta, quando a chamada marca falta sem aviso. O sessao.cancelada traz em alunos quem estava na aula e saiu com reposição em aberto (participacaoId, pessoaId, pessoa, telefone, origem): é a lista para avisar. Esses alunos não geram participacao.cancelada, que continua querendo dizer desistência. Ignore evento que você não conhece, porque outros vão aparecer.

POST no seu endereço
Verandi-Event: participacao.cancelada
Verandi-Timestamp: 1786820400
Verandi-Signature: 9f86d0818...

{
  "evento": "participacao.cancelada",
  "eventoId": "b7c2...",
  "criadoEm": "2026-08-17T12:03:00.000Z",
  "dados": {
    "sessaoId": "a41f...", "data": "2026-08-18", "hora": "07:00",
    "servico": "Pilates solo", "profissional": "Marina",
    "participacaoId": "5e90...", "status": "falta_avisada", "origem": "avulso",
    "pessoaId": "77c0...", "pessoa": "Marina Alves", "telefone": "11988887777"
  }
}

Confira a assinatura antes de confiar

A assinatura é um HMAC SHA-256 do texto instante.corpo, com o seu segredo. O instante entra na conta, e não só no cabeçalho: sem isso, quem gravasse uma entrega poderia repeti-la amanhã com a assinatura ainda válida. Recuse o que tiver mais de 5 minutos.

const bruto = await req.text()
const instante = req.headers.get('Verandi-Timestamp')
const esperado = crypto.createHmac('sha256', SEGREDO)
  .update(`${instante}.${bruto}`).digest('hex')

if (esperado !== req.headers.get('Verandi-Signature')) return new Response(null, { status: 401 })
if (Math.abs(Date.now() / 1000 - Number(instante)) > 300) return new Response(null, { status: 401 })

Responda rápido, e trate repetição

Qualquer resposta 2xx encerra a entrega. Qualquer outra coisa, ou 10 segundos sem resposta, faz a Verandi tentar de novo em 30 segundos, 2 minutos, 5 minutos, 15 minutos, 1 hora e 2 horas. Depois disso ela desiste e registra o erro. Como a reentrega existe, o mesmo evento pode chegar duas vezes: use o eventoId para descartar o que você já processou.