# 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" ``` > **Dica**: 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`](https://developers.festero.com.br/errors.md#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: 1. Na primeira vez, percorra a lista inteira, sem `updatedSince`. 2. Anote o maior instante de alteração visto: o `updatedAt` de cada item, ou o `createdAt` quando `updatedAt` for `null`. 3. Nas próximas, chame com `updatedSince=` e percorra a lista. 4. **Deduplique pelo `id`.** O filtro `updatedSince` é 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](https://developers.festero.com.br/webhooks.md) (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](https://developers.festero.com.br/webhooks/eventos.md) 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êm `null` quando não se aplicam. ## Status e textos - Os status vêm em **minúsculas**: `open`, `upcoming`, `partiallypaid`. Cada rota lista os valores aceitos no filtro `status`, que é validado: um valor desconhecido responde `400` com 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](https://developers.festero.com.br/versionamento.md). ## 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." } ] } ```