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.

Parâmetros

telefonetextocom DDD; aceita máscara e o país
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. Observação e data de nascimento nunca aparecem aqui: são dados de ficha, e ficha é da tela.

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", "servico": "Pilates solo", "origem": "recorrente", "status": "esperada" }
  ],
  "reposicoesAbertas": [
    { "participacaoId": "1c44...", "data": "2026-08-04", "hora": "07:00",
      "servico": "Pilates solo", "motivo": "falta_avisada" }
  ]
}
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"
}
DELETE/participacoes/{participacaoId}

Desmarcar

Registra que 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 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.

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
}
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
}

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. Quatro eventos hoje: participacao.criada, participacao.cancelada, sessao.cancelada e vaga.aberta, este último quando abre vaga em horário que tinha fila. 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.