Aparência
Paginação e datas
Esta página reúne as convenções que valem para todas as rotas: como percorrer uma lista, como pegar só o que mudou e como ler datas, horários, valores em dinheiro e status.
Listas paginadas
Toda rota que devolve uma lista (GET /v1/events, GET /v1/quotes e as demais) responde com a mesma forma:
json
{
"data": [ { "id": "..." } ],
"nextCursor": "eyJ0IjozOTU0..."
}| Campo | Significado |
|---|---|
data | Os itens da página. |
nextCursor | O cursor da próxima página. É null na última página. |
Os parâmetros de paginação são os mesmos em todas as listas:
| Parâmetro | Tipo | Padrão | Descrição |
|---|---|---|---|
limit | inteiro de 1 a 100 | 25 | Tamanho da página. Fora desse intervalo a API responde 400. |
cursor | texto | nenhum | O nextCursor da página anterior. Sem ele, a lista começa do início. |
updatedSince | data e hora ISO 8601 com deslocamento | nenhum | Só os itens alterados a partir deste instante (inclusive). |
Mantenha os outros filtros iguais ao pedir a página seguinte; só o cursor muda.
Percorrer tudo
Repita a chamada com o nextCursor até ele vir null:
bash
# Primeira página
curl "https://platform.festero.com.br/v1/quotes?status=sent&limit=100" \
-H "Authorization: Bearer $ACCESS_TOKEN"
# Seguintes: o mesmo pedido, mais o cursor recebido
curl "https://platform.festero.com.br/v1/quotes?status=sent&limit=100&cursor=eyJ0IjozOTU0..." \
-H "Authorization: Bearer $ACCESS_TOKEN"O cursor é opaco
Não tente ler nem montar um cursor: guarde e devolva exatamente o valor recebido. Um cursor adulterado ou de outra versão da API faz a chamada responder 400 com o código validation_failed e o campo cursor em errors.
Ordem
As listas vêm da alteração mais antiga para a mais recente: ordenadas por updatedAt (ou por createdAt, enquanto o item nunca foi alterado) e, em caso de empate, pelo id. Não há ordenação inversa. Para chegar ao que mudou há pouco, use updatedSince em vez de percorrer a lista inteira.
Sincronização incremental
Para manter uma cópia local atualizada sem reler tudo:
- Na primeira vez, percorra a lista inteira, sem
updatedSince. - Anote o maior instante de alteração visto: o
updatedAtde cada item, ou ocreatedAtquandoupdatedAtfornull. - Nas próximas, chame com
updatedSince=<esse instante>e percorra a lista. - Deduplique pelo
id. O filtroupdatedSinceé inclusivo (maior ou igual), então o último item da rodada anterior volta na seguinte.
Exemplo completo em Python, para orçamentos:
python
from datetime import datetime
import requests
API = "https://platform.festero.com.br"
def sincronizar(token: str, ultimo_instante: str | None, base_local: dict) -> str | None:
"""Atualiza base_local (id -> orçamento) e devolve o instante da última alteração vista.
Requer Python 3.11 ou mais novo: os instantes da API podem trazer até 7 casas de fração de segundo."""
cabecalhos = {"Authorization": f"Bearer {token}"}
parametros = {"limit": 100}
if ultimo_instante:
parametros["updatedSince"] = ultimo_instante
maior = datetime.fromisoformat(ultimo_instante) if ultimo_instante else None
while True:
resposta = requests.get(f"{API}/v1/quotes", headers=cabecalhos, params=parametros, timeout=30)
resposta.raise_for_status()
pagina = resposta.json()
for orcamento in pagina["data"]:
base_local[orcamento["id"]] = orcamento # o mesmo id sobrescreve: dedupe
texto = orcamento["updatedAt"] or orcamento["createdAt"]
if texto:
instante = datetime.fromisoformat(texto)
if maior is None or instante > maior:
maior = instante
if not pagina["nextCursor"]:
return maior.isoformat() if maior else None
parametros["cursor"] = pagina["nextCursor"]A sincronização incremental enxerga alterações e novidades, mas não vê exclusões: um recurso excluído deixa de aparecer nas listas e responde 404 quando buscado por id. Se a exclusão importa para você, use webhooks (por exemplo, event.cancelled com reason igual a deleted) ou releia a lista de tempos em tempos.
Filtros por data
Cada lista tem filtros próprios por data, sempre no formato yyyy-MM-dd e inclusivos nos dois extremos:
| Rota | Filtros | Aplica-se a |
|---|---|---|
GET /v1/events | eventDateFrom, eventDateTo | Data do evento |
GET /v1/quotes | eventDateFrom, eventDateTo | Data do evento do orçamento |
GET /v1/visits | startFrom, startTo | Início agendado da visita, no horário de Brasília |
GET /v1/finance/receivables | dueFrom, dueTo | Data de vencimento |
GET /v1/finance/sales-summary | from, to (obrigatórios) | Período do resumo, no máximo 366 dias |
Datas e horários
| Tipo | Formato | Exemplo | Onde aparece |
|---|---|---|---|
| Instante | ISO 8601 com deslocamento, no horário de Brasília | 2026-10-19T16:20:05-03:00 | createdAt, updatedAt, sentAt, scheduledStart e demais campos que terminam em At |
| Data | yyyy-MM-dd, sem hora | 2027-03-20 | eventDate, validUntil, dueDate, paidDate, expectedEventDate |
| Horário do evento | Texto, como cadastrado no Festero | 18:00 | eventTime |
Instantes que a API recebe (como updatedSince) aceitam qualquer deslocamento, por exemplo 2026-10-19T19:20:05Z. No curl, codifique o + de um deslocamento positivo como %2B.
Os webhooks usam outro padrão: occurred_at e os horários dentro de data vêm em UTC, com Z no fim.
Dinheiro
Todo valor em dinheiro é um objeto com valor e moeda:
json
{ "amount": 12500.00, "currency": "BRL" }amounté um número decimal com duas casas. Em JavaScript e em outras linguagens com ponto flutuante, trate-o com uma biblioteca decimal, ou converta para centavos inteiros antes de somar.currencyéBRL.- O valor é sempre o preço ao cliente. Custo e margem não existem na API.
- Campos de dinheiro opcionais (como
travelFee) vêmnullquando não se aplicam.
Status e textos
- Os status vêm em minúsculas:
open,upcoming,partiallypaid. Cada rota lista os valores aceitos no filtrostatus, que é validado: um valor desconhecido responde400com os valores aceitos. - Os nomes de campo são
camelCase. Identificadores são GUIDs em texto. - Campos sem valor vêm como
null; não são omitidos. - Nomes e títulos que contenham algo parecido com telefone, e-mail ou documento aparecem com o trecho trocado por
[oculto]. - Seu código deve ignorar campos que não conhece: a API pode ganhar campos novos dentro da
/v1. Veja Versionamento.
Recurso individual: ETag
As rotas que buscam um recurso por id devolvem o cabeçalho ETag. Ao reler o mesmo recurso, envie esse valor em If-None-Match: se nada mudou, a API responde 304 Not Modified, sem corpo.
bash
curl -i https://platform.festero.com.br/v1/events/b7e2c4a1-95d3-4f08-a6b2-1c8d3e5f7a90 \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H 'If-None-Match: "3b1f0c7a9d2e4b68a5c1f0e7d9a24b36"'Erros comuns de validação
Parâmetro inválido responde 400 com o código validation_failed e a lista errors:
json
{
"type": "https://developers.festero.com.br/errors#validation_failed",
"title": "Validation failed",
"status": 400,
"detail": "One or more parameters are invalid.",
"code": "validation_failed",
"requestId": "6f1c2a9e-0b7d-4c1e-9a52-3e8f7d1c0a44",
"errors": [
{ "field": "limit", "message": "limit must be between 1 and 100." }
]
}