API
Ofertas de Voos
Acessar ofertas destacadas e promocionais de voos
Ofertas de Voos
Acesse ofertas pré-selecionadas de voos com preços especiais. Ideal para exibir em home pages, landing pages e seções de destaques.
Ofertas Curadas
As ofertas são pré-selecionadas e otimizadas para conversão, com preços competitivos e rotas populares.
POST /api/ofertas/buscar
Busca ofertas de voos com filtros opcionais.
Request
{
"internacional": true,
"quantidade": 10,
"shuffle": true,
"page": 0,
"pageSize": 20
}Parâmetros
| Parâmetro | Tipo | Obrigatório | Descrição |
|---|---|---|---|
internacional | boolean | Não | Filtrar por voos internacionais (padrão: false) |
quantidade | number | Não | Quantidade de ofertas a retornar (padrão: 50) |
shuffle | boolean | Não | Embaralhar resultados (padrão: true) |
page | number | Não | Número da página para paginação (padrão: 0) |
pageSize | number | Não | Tamanho da página (padrão: 0 = sem paginação) |
Response
{
"success": true,
"data": [
{
"id": "oferta_123",
"origem": {
"iata": "GRU",
"cidade": "São Paulo",
"estado": "SP",
"pais": "Brasil",
"aeroporto": "Aeroporto Internacional de São Paulo/Guarulhos"
},
"destino": {
"iata": "MIA",
"cidade": "Miami",
"estado": "FL",
"pais": "Estados Unidos",
"aeroporto": "Miami International Airport"
},
"precoMinimo": 1299.00,
"moeda": "BRL",
"companhiaAerea": {
"codigo": "LA",
"nome": "LATAM Airlines"
},
"tipo": "round_trip",
"internacional": true,
"dataInicio": "2026-03-01",
"dataFim": "2026-06-30",
"restricoes": [
"Antecedência mínima de 7 dias",
"Permanência mínima de 3 dias"
],
"disponivel": true,
"imagemDestino": "https://images.unsplash.com/..."
}
],
"totalItens": 150,
"page": 0,
"pageSize": 10,
"message": "Ofertas encontradas com sucesso"
}Campos da Resposta
| Campo | Tipo | Descrição |
|---|---|---|
id | string | ID único da oferta |
origem | object | Informações do aeroporto de origem |
destino | object | Informações do aeroporto de destino |
precoMinimo | number | Menor preço encontrado para esta rota |
moeda | string | Moeda do preço (ex: BRL, USD) |
companhiaAerea | object | Companhia aérea principal (código e nome) |
tipo | string | Tipo de viagem: one_way ou round_trip |
internacional | boolean | Se é um voo internacional |
dataInicio | string | Data de início da validade da oferta |
dataFim | string | Data de fim da validade da oferta |
restricoes | array | Lista de restrições da oferta |
disponivel | boolean | Se a oferta está disponível |
imagemDestino | string | URL da imagem do destino (opcional) |
Exemplos de Uso
Ofertas Internacionais
curl -X POST https://api.seudominio.com/api/ofertas/buscar \
-H "Authorization: Bearer sua_chave_aqui" \
-H "Content-Type: application/json" \
-d '{
"internacional": true,
"quantidade": 20,
"shuffle": true
}'Ofertas Nacionais
curl -X POST https://api.seudominio.com/api/ofertas/buscar \
-H "Authorization: Bearer sua_chave_aqui" \
-H "Content-Type: application/json" \
-d '{
"internacional": false,
"quantidade": 15,
"shuffle": false
}'Com Paginação
curl -X POST https://api.seudominio.com/api/ofertas/buscar \
-H "Authorization: Bearer sua_chave_aqui" \
-H "Content-Type: application/json" \
-d '{
"internacional": true,
"page": 1,
"pageSize": 10
}'Implementação em React
import { useState, useEffect } from 'react';
interface Oferta {
id: string;
origem: {
iata: string;
cidade: string;
pais: string;
};
destino: {
iata: string;
cidade: string;
pais: string;
};
precoMinimo: number;
moeda: string;
companhiaAerea: {
codigo: string;
nome: string;
};
imagemDestino?: string;
}
function OfertasVoos() {
const [ofertas, setOfertas] = useState<Oferta[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function carregarOfertas() {
try {
const response = await fetch('/api/ofertas/buscar', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.NEXT_PUBLIC_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
internacional: true,
quantidade: 12,
shuffle: true
})
});
const data = await response.json();
if (data.success) {
setOfertas(data.data);
}
} catch (error) {
console.error('Erro ao carregar ofertas:', error);
} finally {
setLoading(false);
}
}
carregarOfertas();
}, []);
if (loading) {
return <div>Carregando ofertas...</div>;
}
return (
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{ofertas.map((oferta) => (
<div key={oferta.id} className="border rounded-lg overflow-hidden shadow-lg">
{oferta.imagemDestino && (
<img
src={oferta.imagemDestino}
alt={oferta.destino.cidade}
className="w-full h-48 object-cover"
/>
)}
<div className="p-4">
<div className="text-sm text-gray-600">
{oferta.origem.cidade} → {oferta.destino.cidade}
</div>
<div className="text-2xl font-bold mt-2">
{oferta.moeda} {oferta.precoMinimo.toFixed(2)}
</div>
<div className="text-sm text-gray-500 mt-1">
{oferta.companhiaAerea.nome}
</div>
<button className="mt-4 w-full bg-blue-600 text-white py-2 rounded hover:bg-blue-700">
Ver Detalhes
</button>
</div>
</div>
))}
</div>
);
}import { useState, useEffect } from 'react';
function OfertasVoos() {
const [ofertas, setOfertas] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function carregarOfertas() {
try {
const response = await fetch('/api/ofertas/buscar', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.NEXT_PUBLIC_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
internacional: true,
quantidade: 12,
shuffle: true
})
});
const data = await response.json();
if (data.success) {
setOfertas(data.data);
}
} catch (error) {
console.error('Erro ao carregar ofertas:', error);
} finally {
setLoading(false);
}
}
carregarOfertas();
}, []);
if (loading) {
return <div>Carregando ofertas...</div>;
}
return (
<div className="ofertas-grid">
{ofertas.map((oferta) => (
<div key={oferta.id} className="oferta-card">
{oferta.imagemDestino && (
<img src={oferta.imagemDestino} alt={oferta.destino.cidade} />
)}
<div className="oferta-content">
<div className="rota">
{oferta.origem.cidade} → {oferta.destino.cidade}
</div>
<div className="preco">
{oferta.moeda} {oferta.precoMinimo.toFixed(2)}
</div>
<div className="companhia">
{oferta.companhiaAerea.nome}
</div>
<button>Ver Detalhes</button>
</div>
</div>
))}
</div>
);
}Casos de Uso
1. Home Page - Destaques
Exiba ofertas em destaque na página inicial:
// Buscar 6 ofertas internacionais aleatórias
const response = await fetch('/api/ofertas/buscar', {
method: 'POST',
body: JSON.stringify({
internacional: true,
quantidade: 6,
shuffle: true
})
});2. Landing Page - Destino Específico
Filtre ofertas por destino usando os dados retornados:
const ofertas = await buscarOfertas({ quantidade: 50 });
const ofertasParaMiami = ofertas.data.filter(
oferta => oferta.destino.iata === 'MIA'
);3. Newsletter - Ofertas Semanais
Envie ofertas diferentes a cada semana:
// Sem shuffle para manter consistência
const response = await fetch('/api/ofertas/buscar', {
method: 'POST',
body: JSON.stringify({
internacional: true,
quantidade: 10,
shuffle: false
})
});4. Comparador de Preços
Mostre as melhores ofertas por região:
const [nacionais, internacionais] = await Promise.all([
fetch('/api/ofertas/buscar', {
method: 'POST',
body: JSON.stringify({ internacional: false, quantidade: 20 })
}).then(r => r.json()),
fetch('/api/ofertas/buscar', {
method: 'POST',
body: JSON.stringify({ internacional: true, quantidade: 20 })
}).then(r => r.json())
]);Integração com Busca de Voos
Quando um usuário clicar em uma oferta, redirecione para a busca de voos:
function handleOfertaClick(oferta) {
// Calcular data de partida (ex: 30 dias à frente)
const departureDate = new Date();
departureDate.setDate(departureDate.getDate() + 30);
// Calcular data de retorno (ex: 7 dias depois)
const returnDate = new Date(departureDate);
returnDate.setDate(returnDate.getDate() + 7);
// Redirecionar para busca
const searchParams = new URLSearchParams({
origin: oferta.origem.iata,
destination: oferta.destino.iata,
departureDate: departureDate.toISOString().split('T')[0],
returnDate: returnDate.toISOString().split('T')[0],
passengers: '1',
cabinClass: 'economy'
});
window.location.href = `/voos/buscar?${searchParams.toString()}`;
}Dicas de UX
Melhores Práticas
- Embaralhe ofertas para evitar repetição visual
- Exiba imagens atraentes dos destinos
- Destaque o preço como elemento principal
- Adicione CTAs claros como "Ver Voos" ou "Buscar"
- Mostre restrições de forma clara mas não intrusiva
- Use cache de 1-4 horas (ofertas mudam pouco)
Layout Sugerido
┌───────────────────────────────────┐
│ [Imagem do Destino] │
│ │
├───────────────────────────────────┤
│ São Paulo → Miami │
│ │
│ R$ 1.299,00 │
│ LATAM Airlines │
│ │
│ [ Ver Voos ] │
└───────────────────────────────────┘Cache e Performance
Recomendações de Cache
// Próximo.js - Cache de 2 horas
export const revalidate = 7200;
// Ou usar SWR
import useSWR from 'swr';
function OfertasVoos() {
const { data, error } = useSWR(
'ofertas',
() => fetch('/api/ofertas/buscar', {
method: 'POST',
body: JSON.stringify({ internacional: true, quantidade: 12 })
}).then(r => r.json()),
{
refreshInterval: 7200000, // 2 horas
revalidateOnFocus: false
}
);
}Performance
- Latência típica: 200-500ms
- Cache recomendado: 1-4 horas
- Refresh: A cada 2-4 horas para manter ofertas atualizadas
Erros Comuns
| Código | Erro | Solução |
|---|---|---|
401 | Unauthorized | Verifique sua API key |
400 | Invalid parameters | Verifique tipos dos parâmetros |
429 | Rate limit exceeded | Reduza frequência ou implemente cache |
500 | Internal server error | Tente novamente ou contate suporte |
Próximos Passos
Após exibir ofertas, integre com:
- Busca de Voos - Para buscar voos específicos
- Calendário de Preços - Para mostrar variação de preços
- Autocomplete - Para formulários de busca