Microsserviço HTTP (FastAPI) que conecta o Voo Barato (Symfony, apps) ao Google Flights via biblioteca fli.
| Item | Valor |
|---|---|
| Base URL (local) | http://127.0.0.1:12000 |
| Formato | JSON UTF-8 |
| Idioma dos campos | Português (PT-BR) |
| Swagger interativo | http://127.0.0.1:12000/docs |
- Convenções gerais
- Busca aberta — como ler a resposta
- Campos opcionais e padrões
- Erros e códigos HTTP
- Enums aceitos
- Modelos de resposta compartilhados
- Endpoints
- Exemplos de integração
| Cabeçalho | Obrigatório | Rotas | Descrição |
|---|---|---|---|
Content-Type: application/json |
Sim (POST) | Todas POST | Corpo sempre JSON |
X-Internal-Token |
Condicional | Rotas de busca | Obrigatório somente se FLIGHTS_API_INTERNAL_TOKEN estiver definido |
X-Request-ID |
Não | Todas | ID de correlação enviado pelo cliente; se omitido, a API gera um UUID |
X-Correlation-ID |
Não | Todas | Alias aceito no lugar de X-Request-ID |
Toda resposta inclui X-Request-ID no cabeçalho HTTP (gerado ou repassado).
Origens permitidas vêm de CORS_ORIGINS (URLs extras, vírgula) + localhost de dev (sempre ativos).
| Origem | Quando |
|---|---|
localhost:8010, localhost:3000 |
Sempre (dev local) |
URLs em CORS_ORIGINS |
Adicionadas em prod/staging |
CORS_ORIGINS=https://seu-dominio.exemplo.comX-Internal-Token: voobarato_secret_token_12345| Cenário | Comportamento |
|---|---|
| Token configurado + header ausente/errado em rota protegida | 401 → {"error": "nao_autorizado"} |
| Token não configurado no servidor (dev local) | Rotas protegidas ficam abertas |
| Rotas públicas (sem token) | /saude, /api/v1/aeroportos, /api/v1/aeroportos/{iata}, /api/v1/cidades, /api/v1/cities, /api/v1/locais |
| Objetivo | Endpoint |
|---|---|
| Health check / Docker | GET /saude |
| Mostrar preço ao usuário em data exata | POST /api/v1/buscar |
| Alerta sem data — explorar próximos N dias (cidade/estado) | POST /api/v1/buscar/aberta |
| Busca por local com data fixa ou sem data (mesmo comportamento) | POST /api/v1/buscar/por-local |
| Heatmap por IATA em intervalo ou sem datas | POST /api/v1/buscar/janela |
| Autocomplete de aeroportos | GET /api/v1/aeroportos |
| Autocomplete exclusivo de cidades/estados (Voo Barato) | GET /api/v1/cidades ou GET /api/v1/cities |
| Autocomplete unificado (IATA + cidade) | GET /api/v1/locais |
| Variável de ambiente | Padrão | Descrição |
|---|---|---|
OPEN_SEARCH_WINDOW_DAYS |
90 |
Dias escaneados quando nenhuma data é informada (hoje → hoje+N) |
FLIGHTS_HTTPS_PROXY / HTTPS_PROXY |
— | Proxy HTTPS para Google Flights (recomendado em VPS/datacenter) |
A janela se desloca automaticamente a cada requisição — não é um mês fixo. Ideal para alertas recorrentes via cron.
Em servidores de datacenter (Easypanel, AWS, etc.), o Google Flights pode retornar zero ofertas sem erro HTTP. Configure um proxy com IP residencial via FLIGHTS_HTTPS_PROXY. Confirme em GET /saude → upstream.proxy_configurado: true.
Endpoints afetados: POST /api/v1/buscar/aberta e POST /api/v1/buscar/por-local (sem data_partida).
A resposta tem duas camadas. Não confunda resumo com voo completo.
Lista uma entrada por dia dentro da janela, com o menor preço daquele dia (melhor par IATA entre origem × destino).
| Campo | O que é | Serve para |
|---|---|---|
data |
Dia ISO (2026-09-01) |
Mostrar no calendário / WhatsApp |
preco |
Menor preço do dia | Comparar datas rapidamente |
moeda |
Ex: BRL |
Exibição |
aeroporto_origem_iata |
IATA origem do par vencedor | Saber qual aeroporto saiu mais barato |
aeroporto_destino_iata |
IATA destino do par vencedor | Idem destino |
oferta |
Objeto completo do voo ou null |
Detalhes, link, trechos, cidades |
por_datasozinho (semoferta) = só calendário de preços. Isso acontece quandoexpandir_top: 0.
Quando a API expande uma data, busca o voo real naquele dia e preenche oferta com o mesmo JSON da busca por data fixa:
trechos[]— horários, aeroportos, número do voorota_iata[]— rota com IATA + nome da cidadeaeroportos_conexao[]— hubs de escalaurl_busca_google_flights— link direto pro Google Flightsdireto,com_conexao,escalas,companhia_principal, etc.
ofertas[] é a mesma informação em lista plana — um item por data expandida, ordenado por preço.
melhor_oferta = atalho para o item mais barato de ofertas[].
por_data[0].oferta ≈ ofertas[0] (mesmo schema OfertaBuscaPorLocalSaida)
| Valor enviado | Comportamento | por_data[].oferta |
ofertas[] |
|---|---|---|---|
omitido (null) |
Expande todas as datas da janela | Preenchido em cada dia | Todas as ofertas |
0 |
Só calendário, sem buscar voos | sempre null |
[] |
5 |
Expande só as 5 datas mais baratas | preenchido nos 5 primeiros; resto null |
5 ofertas |
30 |
Expande as 30 datas mais baratas | idem | até 30 ofertas |
Janela de 90 dias com
expandir_topomitido = até 90 consultas ao Google Flights (mais lento). Para cron rápido, useexpandir_top: 10ou0+ notificação só com preço/data.
| Necessidade | Campo |
|---|---|
| Listar datas e preços no alerta | por_data[].data + por_data[].preco |
| Link, horário, companhia, rota com cidades | por_data[].oferta ou ofertas[] |
| Melhor opção geral | melhor_oferta |
| Milhas | Calcular no Symfony sobre oferta.preco |
{
"modo_busca": "aberta",
"data_inicio": "2026-08-02",
"data_fim": "2026-11-01",
"janela_dias": 90,
"por_data": [
{
"data": "2026-09-01",
"preco": 400.0,
"moeda": "BRL",
"aeroporto_origem_iata": "VIX",
"aeroporto_destino_iata": "CWB",
"oferta": {
"preco": 400.0,
"moeda": "BRL",
"direto": false,
"com_conexao": true,
"rota_iata": [
{ "iata": "VIX", "cidade": "Vitória" },
{ "iata": "GRU", "cidade": "São Paulo" },
{ "iata": "CWB", "cidade": "Curitiba" }
],
"aeroportos_conexao": [{ "iata": "GRU", "cidade": "São Paulo" }],
"url_busca_google_flights": "https://www.google.com/travel/flights?q=...",
"trechos": [{ "iata_partida": "VIX", "data_hora_partida": "2026-09-01T18:40:00", "...": "..." }],
"aeroporto_origem_iata": "VIX",
"aeroporto_destino_iata": "CWB"
}
},
{
"data": "2026-09-02",
"preco": 420.0,
"moeda": "BRL",
"aeroporto_origem_iata": "VIX",
"aeroporto_destino_iata": "CWB",
"oferta": null
}
],
"ofertas": [ "...mesma oferta de por_data[0].oferta..." ],
"total": 1,
"melhor_oferta": { "...": "..." }
}No exemplo acima, por_data[1].oferta é null porque só a data 2026-09-01 foi expandida (expandir_top: 1). Com expandir_top omitido, todas teriam oferta preenchida.
Regra geral para todos os POST:
| Situação | O que acontece |
|---|---|
| Campo obrigatório omitido | 422 — FastAPI retorna detail[] com "Field required" |
| Campo opcional omitido | Valor padrão do schema é aplicado automaticamente |
Campo opcional enviado como null |
Aceito quando o tipo permite (data_retorno, etc.) |
IATA com minúsculas ("vix") |
Normalizado para maiúsculas ("VIX") |
Enum desconhecido (maximo_escalas: "DIRECT") |
Ignorado silenciosamente — cai no fallback "ANY" |
Data inválida ("10/08/2026") |
422 — formato deve ser YYYY-MM-DD |
expandir_top omitido em /aberta ou /por-local |
Expande todas as datas — por_data[].oferta + ofertas[] completos |
expandir_top: 0 em /aberta ou /por-local |
Só calendário — por_data sem voo, ofertas: [] |
data_partida omitida em /por-local |
Modo aberto — escaneia próximos N dias |
data_inicio/data_fim omitidas em /janela |
Modo aberto por IATA — hoje → hoje+N |
data_retorno sem data_partida em /por-local |
422 — retorno exige ida |
A API usa dois formatos de erro:
Campos malformados, tipos errados, regras de negócio do schema (ex: data de retorno anterior à ida).
{
"detail": [
{
"type": "missing",
"loc": ["body", "data_partida"],
"msg": "Field required",
"input": { "origem": "VIX", "destino": "GRU" }
}
]
}{
"detail": [
{
"type": "value_error",
"loc": ["body"],
"msg": "Value error, data_retorno não pode ser anterior a data_partida",
"input": { "origem": "VIX", "destino": "GRU", "data_partida": "2026-08-10", "data_retorno": "2026-08-05" }
}
]
}{ "error": "mensagem descritiva em português" }| HTTP | error típico |
Quando |
|---|---|---|
401 |
nao_autorizado |
Token inválido ou ausente |
404 |
Aeroporto com código IATA 'ZZZ' não encontrado. |
IATA inexistente em /aeroportos/{iata} |
422 |
Código IATA desconhecido para o fli: ZZZ |
IATA não reconhecido pelo Google Flights na busca |
422 |
Não foi possível encontrar aeroportos para a origem '...' |
Cidade/estado sem aeroporto resolvível em /por-local |
502 |
upstream_search_failed |
Falha na comunicação com Google Flights |
Não é erro. Retorna 200 com total: 0 e ofertas: [].
Em /por-local e /aberta, todas_combinacoes[] indica a causa:
sucesso |
mensagem_erro |
Significado |
|---|---|---|
false |
upstream_sem_resultados |
Google Flights respondeu sem voos (comum em IP de datacenter bloqueado) |
false |
"502..." / exceção |
Falha real na busca upstream |
true |
null |
Par IATA retornou ofertas |
Exemplo — rota sem voo direto com maximo_escalas: "NON_STOP":
{
"origem": "VIX",
"destino": "GYN",
"data_partida": "2026-08-10",
"data_retorno": null,
"moeda": "BRL",
"ofertas": [],
"total": 0
}| Valor | Significado |
|---|---|
ECONOMY |
Econômica |
PREMIUM_ECONOMY |
Econômica premium |
BUSINESS |
Executiva |
FIRST |
Primeira classe |
| Valor | Significado | Se omitir |
|---|---|---|
ANY |
Qualquer número de escalas | (padrão) — inclui conexões |
NON_STOP |
Só voos diretos | Rotas sem voo direto → lista vazia |
ONE_STOP |
Máximo 1 escala | — |
TWO_PLUS_STOPS |
Máximo 2 escalas | — |
| Valor | Significado |
|---|---|
CHEAPEST |
Menor preço |
BEST |
Melhor custo-benefício |
TOP_FLIGHTS |
Voos mais populares |
DURATION |
Menor duração |
DEPARTURE_TIME |
Horário de partida |
ARRIVAL_TIME |
Horário de chegada |
| Valor | *_valor esperado |
Exemplo |
|---|---|---|
cidade |
Nome da cidade (match exato, ignora acentos) | "Vitória", "São Paulo" |
estado |
Sigla UF ou nome por extenso | "ES", "Goiás" |
aeroporto |
Código IATA de 3 letras | "GRU", "VIX" |
Presente em /buscar, /buscar/janela (mais_baratas_expandidas), /buscar/por-local e /buscar/aberta (ofertas[] e por_data[].oferta).
| Campo | Tipo | Descrição |
|---|---|---|
preco |
float |
Preço total (só ida ou ida+volta, conforme a busca) |
moeda |
string |
Ex: "BRL" |
duracao_minutos |
int |
Duração total |
escalas |
int |
Número de escalas |
direto |
bool |
true = sem escalas |
com_conexao |
bool |
true = passa por hub(s) |
rota_iata |
array |
[{ "iata": "VIX", "cidade": "Vitória" }, ...] |
aeroportos_conexao |
array |
Hubs intermediários (mesmo formato) |
companhia_principal |
string |
Companhia principal |
nome_companhia_principal |
string|null |
Nome comercial |
trechos |
array |
Pernas do voo (ver abaixo) |
url_busca_google_flights |
string |
Link Google Flights (one way ou returning) |
encontrado_em |
datetime |
UTC — momento da consulta |
fonte |
string |
Sempre "fli" |
| Campo | Tipo | Descrição |
|---|---|---|
iata_partida / iata_chegada |
string |
Código IATA |
aeroporto_partida / aeroporto_chegada |
string |
Nome completo do aeroporto |
data_hora_partida / data_hora_chegada |
datetime |
Horário local do aeroporto (sem Z/timezone) |
companhia_aerea |
string |
Companhia do trecho |
numero_voo |
string|null |
Número do voo |
duracao_minutos |
int|null |
Duração do trecho |
aeronave |
string|null |
Modelo |
companhia_operadora |
string|null |
Operadora (codeshare) |
Preço só ida vs ida e volta: com
data_retorno: null, o preço é apenas da ida. No Google Flights, o mesmo trecho pode custar ~metade de um pacote ida e volta. Compare sempre o mesmo tipo de viagem.
Horários:
"2026-08-10T18:40:00"= 18h40 no fuso local do aeroporto, não UTC.
Health check. Rota pública — não exige token.
Sem corpo. Sem parâmetros.
curl http://127.0.0.1:12000/saude| Campo | Tipo | Descrição |
|---|---|---|
status |
string |
Sempre "ok" |
servico |
string |
Nome do serviço |
versao |
string |
Versão da API |
upstream.proxy_configurado |
bool |
true se FLIGHTS_HTTPS_PROXY ou HTTPS_PROXY está definido |
{
"status": "ok",
"servico": "voobarato-flights-api",
"versao": "1.0.0",
"upstream": {
"proxy_configurado": false
}
}Busca pontual em data exata. Use para exibir preço ao usuário ou validar alerta em tempo real.
curl -X POST http://127.0.0.1:12000/api/v1/buscar \
-H "Content-Type: application/json" \
-H "X-Internal-Token: voobarato_secret_token_12345" \
-d '{
"origem": "VIX",
"destino": "GRU",
"data_partida": "2026-08-10"
}'Campos enviados: 3 obrigatórios.
Comportamento: busca só ida, 1 adulto, econômica, qualquer escala, ordenado por preço, até 10 ofertas.
curl -X POST http://127.0.0.1:12000/api/v1/buscar \
-H "Content-Type: application/json" \
-H "X-Internal-Token: voobarato_secret_token_12345" \
-d '{
"origem": "VIX",
"destino": "GRU",
"data_partida": "2026-08-10",
"data_retorno": "2026-08-14",
"adultos": 2,
"criancas": 1,
"classe_cabine": "ECONOMY",
"maximo_escalas": "NON_STOP",
"ordenar_por": "CHEAPEST",
"limite_top": 5
}'curl -X POST http://127.0.0.1:12000/api/v1/buscar \
-H "Content-Type: application/json" \
-d '{
"origem": "VIX",
"destino": "GRU",
"data_partida": "2026-08-10",
"maximo_escalas": "NON_STOP"
}'| Campo | Tipo | Obrig. | Padrão | Se omitir | Se inválido |
|---|---|---|---|---|---|
origem |
string |
Sim | — | 422 Field required |
IATA ≠ 3 letras → 422; IATA desconhecido → 422 upstream |
destino |
string |
Sim | — | 422 Field required |
Idem origem |
data_partida |
string |
Sim | — | 422 Field required |
Formato ≠ YYYY-MM-DD → 422 |
data_retorno |
string |
Não | null (só ida) |
Busca só ida; preço de ida | Anterior a data_partida → 422 |
adultos |
int |
Não | 1 |
1 adulto | < 1 ou > 9 → 422 |
criancas |
int |
Não | 0 |
Sem crianças | < 0 ou > 8 → 422 |
classe_cabine |
string |
Não | "ECONOMY" |
Econômica | Valor desconhecido → tratado como ECONOMY |
maximo_escalas |
string |
Não | "ANY" |
Aceita conexões | "NON_STOP" em rota sem direto → ofertas: [] |
ordenar_por |
string |
Não | "CHEAPEST" |
Ordena por preço | Valor desconhecido → CHEAPEST |
limite_top |
int |
Não | 10 |
Até 10 ofertas | < 1 ou > 50 → 422 |
| Campo | Tipo | Descrição |
|---|---|---|
origem |
string |
IATA origem (normalizado) |
destino |
string |
IATA destino |
data_partida |
string |
Data ISO |
data_retorno |
string|null |
null se só ida |
moeda |
string |
Moeda configurada (ex: BRL) |
ofertas |
array |
Lista de OfertaVooSaida, ordenada por preço |
total |
int |
Quantidade de ofertas |
{
"origem": "VIX",
"destino": "GRU",
"data_partida": "2026-08-10",
"data_retorno": null,
"moeda": "BRL",
"total": 2,
"ofertas": [
{
"preco": 452.0,
"moeda": "BRL",
"duracao_minutos": 100,
"escalas": 0,
"direto": true,
"com_conexao": false,
"rota_iata": [
{ "iata": "VIX", "cidade": "Vitória" },
{ "iata": "GRU", "cidade": "São Paulo" }
],
"aeroportos_conexao": [],
"companhia_principal": "Gol Transportes Aéreos",
"nome_companhia_principal": "Gol",
"trechos": [
{
"iata_partida": "VIX",
"iata_chegada": "GRU",
"aeroporto_partida": "Eurico de Aguiar Salles Airport",
"aeroporto_chegada": "Guarulhos - Governador Andre Franco Montoro International Airport",
"companhia_aerea": "Gol Transportes Aéreos",
"numero_voo": "1387",
"data_hora_partida": "2026-08-10T18:40:00",
"data_hora_chegada": "2026-08-10T20:20:00",
"duracao_minutos": 100,
"aeronave": "Boeing 737",
"companhia_operadora": null
}
],
"url_busca_google_flights": "https://www.google.com/travel/flights?q=Flights+from+VIX+to+GRU+on+2026-08-10+one+way&hl=pt-BR&gl=BR&curr=BRL",
"encontrado_em": "2026-08-01T01:00:00.000000Z",
"fonte": "fli"
},
{
"preco": 372.0,
"escalas": 1,
"direto": false,
"com_conexao": true,
"rota_iata": [
{ "iata": "VIX", "cidade": "Vitória" },
{ "iata": "SSA", "cidade": "Salvador" },
{ "iata": "GRU", "cidade": "São Paulo" }
],
"aeroportos_conexao": [{ "iata": "SSA", "cidade": "Salvador" }],
"trechos": ["..."]
}
]
}Voos diretos e com conexão podem coexistir na mesma rota. A lista vem por preço — o mais barato pode ser conexão mesmo existindo voo direto.
Preço mínimo por dia em um intervalo. Ideal para alertas e calendário de preços.
Também aceita modo aberto (sem datas) por IATA — use /buscar/aberta se a origem/destino forem cidade/estado.
Não use para exibir "preço agora" ao usuário sem expandir a data vencedora via /buscar.
curl -X POST http://127.0.0.1:12000/api/v1/buscar/janela \
-H "Content-Type: application/json" \
-d '{
"origem": "VIX",
"destino": "GRU",
"data_inicio": "2026-08-02",
"data_fim": "2026-08-08"
}'Resposta: modo_busca: "janela".
curl -X POST http://127.0.0.1:12000/api/v1/buscar/janela \
-H "Content-Type: application/json" \
-d '{
"origem": "VIX",
"destino": "GRU",
"expandir_top": 0
}'Se omitir data_inicio e data_fim: escaneia hoje → hoje + OPEN_SEARCH_WINDOW_DAYS (padrão 90).
Resposta: modo_busca: "aberta", janela_dias preenchido.
curl -X POST http://127.0.0.1:12000/api/v1/buscar/janela \
-H "Content-Type: application/json" \
-d '{
"origem": "VIX",
"destino": "GRU",
"janela_dias": 30,
"expandir_top": 3
}'Se omitir datas mas enviar janela_dias: escaneia hoje → hoje + 30 dias.
curl -X POST http://127.0.0.1:12000/api/v1/buscar/janela \
-H "Content-Type: application/json" \
-d '{
"origem": "VIX",
"destino": "GRU",
"data_inicio": "2026-08-02",
"data_fim": "2026-08-08",
"expandir_top": 0
}'Se expandir_top: 0: mais_baratas_expandidas retorna []. Só vem por_data.
curl -X POST http://127.0.0.1:12000/api/v1/buscar/janela \
-H "Content-Type: application/json" \
-d '{
"origem": "VIX",
"destino": "GRU",
"data_inicio": "2026-08-02",
"data_fim": "2026-08-08",
"expandir_top": 3
}'| Campo | Tipo | Obrig. | Padrão | Se omitir | Se inválido |
|---|---|---|---|---|---|
origem |
string |
Sim | — | 422 |
IATA inválido → 422 |
destino |
string |
Sim | — | 422 |
IATA inválido → 422 |
data_inicio |
string |
Não | null |
Modo aberto (hoje) | Formato inválido → 422 |
data_fim |
string |
Não | null |
Modo aberto (hoje+N) | Anterior a data_inicio → 422 |
janela_dias |
int |
Não | null |
Usa OPEN_SEARCH_WINDOW_DAYS (90) |
1–180; fora → 422 |
adultos |
int |
Não | 1 |
1 adulto | Fora de 1–9 → 422 |
classe_cabine |
string |
Não | "ECONOMY" |
Econômica | — |
maximo_escalas |
string |
Não | "ANY" |
Aceita conexões | — |
expandir_top |
int |
Não | 1 |
Expande 1 data | 0 = sem expansão; max 10 |
Janela não suporta
data_retorno— sempre busca só ida por dia.
Se informar sódata_inicio(semdata_fim): intervalo =data_inicio→data_inicio + janela_dias.
| Campo | Tipo | Descrição |
|---|---|---|
origem / destino |
string |
IATAs |
data_inicio / data_fim |
string |
Intervalo efetivo (calculado se omitido) |
moeda |
string |
Moeda |
modo_busca |
string |
"janela" (datas informadas) ou "aberta" (sem datas) |
janela_dias |
int|null |
Preenchido no modo aberto |
por_data |
array |
{ data, preco, moeda } por dia, ordenado por preço |
mais_baratas_expandidas |
array |
OfertaVooSaida das N datas mais baratas |
{
"origem": "VIX",
"destino": "GRU",
"data_inicio": "2026-08-02",
"data_fim": "2026-08-09",
"moeda": "BRL",
"modo_busca": "aberta",
"janela_dias": 7,
"por_data": [
{ "data": "2026-08-09", "preco": 452.0, "moeda": "BRL" },
{ "data": "2026-08-06", "preco": 846.0, "moeda": "BRL" }
],
"mais_baratas_expandidas": []
}Atalho para busca sem data por cidade, estado ou aeroporto — mesmo contrato de entrada que /buscar/por-local, mas sem data_partida.
Equivalente a chamar /buscar/por-local omitindo a data. Retorna RespostaBuscaPorLocal com modo_busca: "aberta".
Use para alertas recorrentes em que o usuário configurou rota por nome de cidade, sem data de ida nem volta.
curl -X POST http://127.0.0.1:12000/api/v1/buscar/aberta \
-H "Content-Type: application/json" \
-d '{
"origem_valor": "Vitória",
"destino_valor": "Goiânia"
}'Comportamento: resolve aeroportos, escaneia 90 dias (padrão), expande todas as datas com oferta completa (expandir_top omitido).
Ver Busca aberta — como ler a resposta para entender
por_datavsofertavsofertas[].
curl -X POST http://127.0.0.1:12000/api/v1/buscar/aberta \
-H "Content-Type: application/json" \
-d '{
"origem_valor": "Vitória",
"destino_valor": "Curitiba",
"janela_dias": 30,
"expandir_top": 0
}'Retorna por_data[] com data/preço/IATA — oferta: null em todos, ofertas: [].
curl -X POST http://127.0.0.1:12000/api/v1/buscar/aberta \
-H "Content-Type: application/json" \
-d '{
"origem_tipo": "estado",
"origem_valor": "Espírito Santo",
"destino_tipo": "estado",
"destino_valor": "GO",
"janela_dias": 60,
"expandir_top": 0
}'| Campo | Tipo | Obrig. | Padrão | Se omitir | Se inválido |
|---|---|---|---|---|---|
origem_tipo |
string |
Não | "cidade" |
Assume cidade | Enum inválido → 422 |
origem_valor |
string |
Sim | — | 422 |
Cidade/estado sem aeroporto → 422 |
destino_tipo |
string |
Não | "cidade" |
Assume cidade | — |
destino_valor |
string |
Sim | — | 422 |
Sem aeroporto → 422 |
janela_dias |
int |
Não | 90 (env) |
Usa OPEN_SEARCH_WINDOW_DAYS |
1–180 |
adultos |
int |
Não | 1 |
1 adulto | 1–9 |
classe_cabine |
string |
Não | "ECONOMY" |
Econômica | — |
maximo_escalas |
string |
Não | "ANY" |
Aceita conexões | — |
expandir_top |
int|null |
Não | null (todas) |
Expande todas as datas | 0 = só resumo; N = top N; max 180 |
Valores de origem_tipo / destino_tipo: "cidade", "estado", "aeroporto" (IATA em *_valor).
Mesmo formato de POST /api/v1/buscar/por-local no modo aberto (modo_busca: "aberta").
| Onde está o quê | Campo |
|---|---|
| Calendário dia a dia | por_data[] |
| Voo completo (link, trechos, cidades) | por_data[].oferta ou ofertas[] |
| Melhor preço geral | melhor_oferta |
Documentação detalhada: Busca aberta — como ler a resposta.
Busca por cidade, estado ou aeroporto — o usuário não precisa saber IATA.
Suporta dois modos via modo_busca na resposta:
| Modo | Quando | O que retorna |
|---|---|---|
data_fixa |
data_partida informada |
ofertas[] com todos os voos na data — sem por_data |
aberta |
data_partida omitida/null |
por_data[] (calendário) + por_data[].oferta (voo completo) + ofertas[] |
- Resolve origem e destino em até 3 aeroportos principais cada
- Gera combinações (ex: Vitória × São Paulo → VIX→GRU, VIX→CGH)
- Busca em paralelo (até 4 threads)
- Cache de 5 min por par IATA+data
- Retorna todas as ofertas de todas as combinações, ordenadas por preço
- Resolve aeroportos (igual acima)
- Para cada par IATA, consulta preço mínimo por dia na janela (hoje → hoje+N)
- Consolida
por_data[]— por data, fica o par mais barato - Expande cada data (
expandir_top) com oferta completa empor_data[].ofertaeofertas[]
curl -X POST http://127.0.0.1:12000/api/v1/buscar/por-local \
-H "Content-Type: application/json" \
-d '{
"origem_tipo": "cidade",
"origem_valor": "Vitória",
"destino_tipo": "cidade",
"destino_valor": "Goiânia",
"data_partida": "2026-08-10"
}'curl -X POST http://127.0.0.1:12000/api/v1/buscar/por-local \
-H "Content-Type: application/json" \
-d '{
"origem_valor": "Vitória",
"destino_valor": "Goiânia",
"janela_dias": 90,
"expandir_top": 5
}'Se omitir data_partida: modo aberto — não envie data_retorno.
| Campo | Conteúdo |
|---|---|
por_data[] |
Uma linha por dia: data, preco, IATAs + oferta (voo completo ou null) |
ofertas[] |
Lista plana das ofertas expandidas (mesmo JSON de por_data[].oferta) |
total |
Quantidade de itens em ofertas[] |
Controle de expansão: expandir_top.
curl -X POST http://127.0.0.1:12000/api/v1/buscar/por-local \
-H "Content-Type: application/json" \
-d '{
"origem_tipo": "estado",
"origem_valor": "Espírito Santo",
"destino_tipo": "estado",
"destino_valor": "GO",
"data_partida": "2026-08-10"
}'curl -X POST http://127.0.0.1:12000/api/v1/buscar/por-local \
-H "Content-Type: application/json" \
-d '{
"origem_tipo": "cidade",
"origem_valor": "Vitória",
"destino_tipo": "cidade",
"destino_valor": "São Paulo",
"data_partida": "2026-08-10",
"data_retorno": "2026-08-14"
}'| Campo | Tipo | Obrig. | Padrão | Se omitir | Se inválido |
|---|---|---|---|---|---|
origem_tipo |
string |
Não | "cidade" |
Assume cidade | Valor fora do enum → 422 |
origem_valor |
string |
Sim | — | 422 |
Cidade/estado sem aeroporto → 422 |
destino_tipo |
string |
Não | "cidade" |
Assume cidade | — |
destino_valor |
string |
Sim | — | 422 |
Sem aeroporto resolvível → 422 |
data_partida |
string |
Não | null |
Modo aberto | Formato inválido → 422 |
data_retorno |
string |
Não | null |
Só ida | Sem data_partida → 422; anterior à ida → 422 |
janela_dias |
int |
Não | 90 (env) |
Usa OPEN_SEARCH_WINDOW_DAYS |
1–180; só modo aberto |
expandir_top |
int|null |
Não | null (todas) |
Expande todas as datas | 0 = só resumo; max 180; só modo aberto |
adultos |
int |
Não | 1 |
1 adulto | 1–9 |
criancas |
int |
Não | 0 |
Sem crianças | 0–8; só modo data_fixa |
classe_cabine |
string |
Não | "ECONOMY" |
Econômica | — |
maximo_escalas |
string |
Não | "ANY" |
Aceita conexões | "NON_STOP" pode zerar ofertas |
ordenar_por |
string |
Não | "CHEAPEST" |
Por preço | Só modo data_fixa |
limite_top |
int |
Não | 20 |
20 ofertas/combo | 1–50; só modo data_fixa |
*_valor |
Aeroportos resolvidos | Observação |
|---|---|---|
"Vitória" |
VIX |
Uma cidade, um aeroporto principal |
"São Paulo" |
GRU, CGH |
VCP fica em "Campinas", não em São Paulo |
"Campinas" |
VCP |
Viracopos |
"Goiânia" |
GYN |
— |
"GRU" (tipo aeroporto) |
GRU |
IATA direto |
| Campo | Tipo | Descrição |
|---|---|---|
origem_buscada / destino_buscado |
string |
Texto enviado pelo cliente |
modo_busca |
string |
"data_fixa" ou "aberta" |
data_partida / data_retorno |
string|null |
Preenchidos no modo data_fixa |
data_inicio / data_fim |
string|null |
Preenchidos no modo aberta |
janela_dias |
int|null |
Preenchido no modo aberta |
moeda |
string |
Moeda |
por_data |
array |
Calendário de preços (modo aberta); vazio no modo data_fixa |
ofertas |
array |
Ofertas completas (OfertaBuscaPorLocalSaida), por preço |
total |
int |
Quantidade em ofertas |
melhor_oferta |
object|null |
Atalho — oferta mais barata |
aeroporto_origem_usado |
string|null |
IATA origem da melhor oferta |
aeroporto_destino_usado |
string|null |
IATA destino da melhor oferta |
todas_combinacoes |
array |
Resumo por par IATA testado |
por_data[] (modo aberto):
| Campo | Descrição |
|---|---|
data |
Data ISO |
preco |
Menor preço naquela data (entre todos os pares IATA) |
moeda |
Moeda |
aeroporto_origem_iata |
Par vencedor — origem |
aeroporto_destino_iata |
Par vencedor — destino |
oferta |
OfertaBuscaPorLocalSaida completa (trechos, rota_iata, url_busca_google_flights, etc.) ou null se não expandida |
OfertaBuscaPorLocalSaida = OfertaVooSaida + campos:
| Campo extra | Descrição |
|---|---|
aeroporto_origem_iata |
IATA origem desta oferta |
aeroporto_destino_iata |
IATA destino desta oferta |
todas_combinacoes[]:
| Campo | Descrição |
|---|---|
origem_iata / destino_iata |
Par testado |
preco_minimo |
Menor preço daquele par (null se falhou) |
sucesso |
true / false |
mensagem_erro |
Preenchido se sucesso: false (ex: upstream_sem_resultados) |
{
"origem_buscada": "Vitória",
"destino_buscado": "Goiânia",
"modo_busca": "data_fixa",
"data_partida": "2026-08-10",
"data_retorno": null,
"data_inicio": null,
"data_fim": null,
"janela_dias": null,
"moeda": "BRL",
"por_data": [],
"total": 120,
"ofertas": ["..."],
"melhor_oferta": { "...": "igual à oferta mais barata" },
"aeroporto_origem_usado": "VIX",
"aeroporto_destino_usado": "GYN",
"todas_combinacoes": ["..."]
}Exemplo — modo aberto (com oferta expandida):
{
"origem_buscada": "Vitória",
"destino_buscado": "Curitiba",
"modo_busca": "aberta",
"data_inicio": "2026-08-02",
"data_fim": "2026-11-01",
"janela_dias": 90,
"moeda": "BRL",
"por_data": [
{
"data": "2026-09-01",
"preco": 400.0,
"moeda": "BRL",
"aeroporto_origem_iata": "VIX",
"aeroporto_destino_iata": "CWB",
"oferta": {
"preco": 400.0,
"direto": false,
"com_conexao": true,
"rota_iata": [
{ "iata": "VIX", "cidade": "Vitória" },
{ "iata": "CWB", "cidade": "Curitiba" }
],
"url_busca_google_flights": "https://www.google.com/travel/flights?q=...",
"trechos": ["..."],
"aeroporto_origem_iata": "VIX",
"aeroporto_destino_iata": "CWB"
}
}
],
"total": 1,
"ofertas": ["...igual a por_data[0].oferta..."],
"melhor_oferta": { "preco": 400.0, "...": "..." },
"todas_combinacoes": ["..."]
}Autocomplete e filtros sobre ~7.800 aeroportos. Todos os parâmetros são opcionais.
curl "http://127.0.0.1:12000/api/v1/aeroportos"Se omitir tudo: retorna até 20 aeroportos (ordem interna do índice).
curl "http://127.0.0.1:12000/api/v1/aeroportos?busca=vitoria&limite=5"busca: match parcial em IATA, nome, cidade ou estado. Ignora acentos (vitoria = Vitória).
curl "http://127.0.0.1:12000/api/v1/aeroportos?cidade=Goi%C3%A2nia"Diferença busca vs cidade: busca é amplo; cidade exige match exato no nome da cidade.
curl "http://127.0.0.1:12000/api/v1/aeroportos?estado=GO&apenas_principais=true"| Parâmetro | Tipo | Obrig. | Padrão | Se omitir | Se inválido |
|---|---|---|---|---|---|
busca |
string |
Não | null |
Sem filtro textual | String vazia → 422 |
cidade |
string |
Não | null |
— | String vazia → 422 |
estado |
string |
Não | null |
— | Aceita "GO" ou "Goiás" |
pais |
string |
Não | null |
— | — |
apenas_principais |
bool |
Não | false |
Inclui secundários | — |
limite |
int |
Não | 20 |
Máx 20 resultados | 1–100 |
Filtros combinam (AND): ?cidade=São Paulo&apenas_principais=true.
| Campo | Tipo | Descrição |
|---|---|---|
total |
int |
Total encontrado (antes do limite) |
aeroportos |
array |
Até limite itens |
aeroportos[]:
| Campo | Tipo | Descrição |
|---|---|---|
codigo_iata |
string |
IATA |
nome |
string |
Nome do aeroporto |
cidade |
string|null |
Cidade |
estado |
string|null |
UF ou região |
pais |
string|null |
País |
principal |
bool |
Aeroporto comercial principal |
descricao_curta |
string|null |
Texto de disambiguação (ex: GRU vs CGH) |
{
"total": 2,
"aeroportos": [
{
"codigo_iata": "GRU",
"nome": "Guarulhos - Governador Andre Franco Montoro International Airport",
"cidade": "São Paulo",
"estado": "SP",
"pais": "Brasil",
"principal": true,
"descricao_curta": "Aeroporto Internacional de Guarulhos — maior hub de voos nacionais e internacionais de SP"
},
{
"codigo_iata": "CGH",
"nome": "Congonhas Airport",
"cidade": "São Paulo",
"estado": "SP",
"pais": "Brasil",
"principal": true,
"descricao_curta": "Aeroporto de Congonhas — próximo ao centro de SP, foco em ponte aérea e voos nacionais"
}
]
}Detalhe de um aeroporto por IATA.
curl "http://127.0.0.1:12000/api/v1/aeroportos/GRU"| Parâmetro | Obrig. | Se inválido |
|---|---|---|
codigo_iata (path) |
Sim | IATA inexistente → 404 |
Mesmo schema de um item de aeroportos[].
{
"error": "Aeroporto com código IATA 'ZZZ' não encontrado."
}Autocomplete e busca de cidades e estados (UF). Usado pelo Voo Barato para busca por localidade.
Também reconhece código IATA de 3 letras na entrada — retorna a cidade correspondente, sem alterar o formato de resposta.
Aliases suportados:
GET /api/v1/cidadesGET /api/v1/citiesGET /api/v1/cidades/buscarGET /api/v1/cities/search
Exemplo de uso:
curl "http://127.0.0.1:12000/api/v1/cidades?busca=são&limite=8"
curl "http://127.0.0.1:12000/api/v1/cities/search?query=GO"
curl "http://127.0.0.1:12000/api/v1/cidades?busca=VIX"
curl "http://127.0.0.1:12000/api/v1/cidades?busca=CGH"busca |
Resultado |
|---|---|
VIX |
Vitória, ES |
CGH |
São Paulo, SP |
vitoria |
Vitória, ES (match por nome) |
GO |
Cidades do Goiás |
| Parâmetro | Tipo | Obrigatório | Valor Padrão | Descrição |
|---|---|---|---|---|
busca / query / q |
string |
Não | null |
Busca por nome da cidade, estado/UF ou código IATA (insensível a acentos/caixa) |
estado / uf |
string |
Não | null |
Filtro estrito por sigla UF ou nome do estado (ex: SP, GO, Goiás) |
pais |
string |
Não | null |
Filtro por país (ex: Brasil) |
limite |
int |
Não | 20 |
Quantidade máxima de resultados (1 a 100) |
| Campo | Tipo | Descrição |
|---|---|---|
total |
int |
Total de cidades encontradas |
cidades |
array |
Lista de cidades até o limite especificado |
Cada item em cidades[]:
| Campo | Tipo | Descrição |
|---|---|---|
nome |
string |
Nome oficial da cidade (ex: "São Paulo", "Goiânia") |
estado |
string|null |
Sigla da UF/Estado (ex: "SP", "GO") |
estado_nome |
string|null |
Nome completo do estado (ex: "São Paulo", "Goiás") |
pais |
string |
País da cidade (ex: "Brasil") |
Exemplo de JSON retornado:
{
"total": 2,
"cidades": [
{
"nome": "Caldas Novas",
"estado": "GO",
"estado_nome": "Goiás",
"pais": "Brasil"
},
{
"nome": "Goiânia",
"estado": "GO",
"estado_nome": "Goiás",
"pais": "Brasil"
}
]
}Autocomplete unificado — retorna códigos IATA e cidades na mesma lista. Ideal para campos de busca onde o usuário pode digitar "VI", "VIX" ou "Vitória".
Aliases:
GET /api/v1/locaisGET /api/v1/locais/buscar
Rota pública — não exige token.
curl "http://127.0.0.1:12000/api/v1/locais?busca=VI&limite=10"
curl "http://127.0.0.1:12000/api/v1/locais?q=VIX"
curl "http://127.0.0.1:12000/api/v1/locais?busca=vitoria"Para busca=VI, retorna tanto VIX (IATA) quanto Vitória (cidade), priorizando aeroportos principais.
| Parâmetro | Tipo | Obrig. | Padrão | Descrição |
|---|---|---|---|---|
busca / query / q |
string |
Não | null |
Termo parcial (IATA ou cidade) |
estado / uf |
string |
Não | null |
Filtro estrito por UF ou nome do estado |
pais |
string |
Não | null |
Filtro por país |
limite |
int |
Não | 20 |
Máximo de resultados (1–100) |
| Campo | Tipo | Descrição |
|---|---|---|
total |
int |
Total encontrado (antes do limite) |
resultados |
array |
Lista mista de IATA e cidades |
Cada item em resultados[]:
| Campo | Tipo | Descrição |
|---|---|---|
tipo |
string |
"iata" ou "cidade" |
valor |
string |
Valor para usar na busca ("VIX" ou "Vitória") |
label |
string |
Texto formatado para exibição |
codigo_iata |
string|null |
Preenchido quando tipo: "iata" |
nome |
string|null |
Nome da cidade |
estado |
string|null |
UF |
estado_nome |
string|null |
Nome do estado |
pais |
string |
País (padrão "Brasil") |
{
"total": 19,
"resultados": [
{
"tipo": "iata",
"valor": "VIX",
"label": "VIX — Vitória, ES",
"codigo_iata": "VIX",
"nome": "Vitória",
"estado": "ES",
"estado_nome": "Espírito Santo",
"pais": "Brasil"
},
{
"tipo": "cidade",
"valor": "Vitória",
"label": "Vitória, ES",
"codigo_iata": null,
"nome": "Vitória",
"estado": "ES",
"estado_nome": "Espírito Santo",
"pais": "Brasil"
}
]
}- IATA exato (
VIX→VIX) - IATA principal com prefixo (
VI→VIX) - Cidade exata
- Cidade com prefixo
- Demais IATAs com prefixo
- Match parcial (contains)
| Endpoint | Use quando |
|---|---|
/cidades |
Symfony/Voo Barato já envia nome de cidade para /buscar/por-local — autocomplete só de localidades |
/locais |
UI permite escolher IATA ou cidade no mesmo campo (ex: usuário digita GRU ou São Paulo) |
$client = new \GuzzleHttp\Client(['base_uri' => 'http://127.0.0.1:12000']);
// Busca só ida — campos mínimos
$response = $client->post('/api/v1/buscar', [
'headers' => [
'X-Internal-Token' => getenv('FLIGHTS_API_INTERNAL_TOKEN'),
'Content-Type' => 'application/json',
],
'json' => [
'origem' => 'VIX',
'destino' => 'GRU',
'data_partida' => '2026-08-10',
],
]);
$dados = json_decode($response->getBody()->getContents(), true);
$ofertas = $dados['ofertas']; // array — pode ser vazio
// Alerta sem data — calendário de preços por cidade
$response = $client->post('/api/v1/buscar/por-local', [
'headers' => ['Content-Type' => 'application/json'],
'json' => [
'origem_valor' => 'Vitória',
'destino_valor' => 'Goiânia',
'janela_dias' => 90,
'expandir_top' => 0,
],
]);
$dados = json_decode($response->getBody()->getContents(), true);
foreach ($dados['por_data'] as $dia) {
echo $dia['data'] . ' — R$ ' . $dia['preco'];
if ($dia['oferta']) {
echo $dia['oferta']['url_busca_google_flights'];
echo $dia['oferta']['rota_iata'][0]['cidade']; // Vitória
}
}
// Alerta sem data — endpoint dedicado
$response = $client->post('/api/v1/buscar/aberta', [
'headers' => ['Content-Type' => 'application/json'],
'json' => [
'origem_valor' => 'Vitória',
'destino_valor' => 'Goiânia',
'expandir_top' => 3,
],
]);
// Autocomplete unificado (IATA + cidade)
$response = $client->get('/api/v1/locais', [
'query' => ['busca' => 'VI', 'limite' => 10],
]);
$locais = json_decode($response->getBody()->getContents(), true)['resultados'];
foreach ($locais as $item) {
// $item['tipo'] === 'iata' | 'cidade'
// $item['valor'] — enviar em origem_valor ou como IATA
}const resposta = await fetch('http://127.0.0.1:12000/api/v1/buscar/por-local', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Internal-Token': process.env.FLIGHTS_API_TOKEN,
},
body: JSON.stringify({
origem_valor: 'Vitória',
destino_valor: 'Goiânia',
// data_partida omitida = modo aberto
janela_dias: 90,
expandir_top: 5,
}),
});
const dados = await resposta.json();
if (dados.modo_busca === 'aberta') {
dados.por_data.forEach((dia) => {
console.log(dia.data, dia.preco, dia.oferta?.url_busca_google_flights);
});
}| Documento | Conteúdo |
|---|---|
| DOCUMENTACAO_TECNICA.md | Estrutura de pastas e arquivos |
| ARCHITECTURE.md | Decisões de arquitetura |
| README.md | Setup rápido |