Windwave APIv1 OpenAPIwindwave.com.br

Vento e câmeras das praias, no seu sistema.

A API do Windwave entrega o vento medido pelas estações nas praias, o histórico dessas medições e as câmeras ao vivo — para rádios, sites de turismo, escolas, apps e painéis de TV.

Endereço: https://windwave.com.br/api/v1 · Respostas em JSON (UTF-8) · Horários em UTC (ISO 8601) · Vento em nós (kt), direção em graus de onde o vento vem, temperatura em °C.

1 · ChaveVocê recebe uma chave ww_live_… da Windwave.
2 · SessãoTroca a chave por um token de 15 min em POST /session.
3 · DadosUsa o token wws_… em todos os pedidos.
4 · RenovaA cada ~10 min, POST /session/refresh. Ao desligar, DELETE /session.

Em 2 minutos

Abra a sessão com a sua chave:

curl -X POST https://windwave.com.br/api/v1/session \
  -H "Authorization: Bearer ww_live_SUA_CHAVE"
{
  "token": "wws_8fK2…",
  "token_type": "Bearer",
  "session_id": "7ed31694-…",
  "expires_at": "2026-11-07T15:27:22Z",
  "expires_in": 900,
  "env": "live"
}

Com o token, peça as estações e o vento de agora:

curl https://windwave.com.br/api/v1/stations -H "Authorization: Bearer wws_8fK2…"
curl https://windwave.com.br/api/v1/stations/{id}/now -H "Authorization: Bearer wws_8fK2…"
{
  "station_id": "5b1c…",
  "at": "2026-11-07T15:12:00Z",
  "age_sec": 42,
  "wind_kt": 18.4,
  "gust_kt": 23.9,
  "dir_deg": 105,
  "temp_c": 28.1,
  "units": { "wind": "kt", "dir": "deg", "temp": "C" }
}

Ainda sem chave de produção? Use uma chave de teste: ela responde com uma estação de demonstração.

Chave e sessão

A chave (ww_live_… ou ww_test_…) é a sua senha de acesso. Ela só serve para abrir a sessão — nunca vai nos outros pedidos. Guarde-a no servidor (variável de ambiente, cofre de segredos), nunca em app de celular, página web ou repositório.

A sessão (wws_…) vale 15 minutos e vai no cabeçalho Authorization: Bearer … de todo pedido.

POST

/session

Abre a sessão. Cabeçalho: Authorization: Bearer ww_live_…. Responde 201 com o token.

POST

/session/refresh

Renova: devolve um token novo com mais 15 minutos, na mesma sessão. O token anterior para de valer na hora — passe a usar o novo. Recomendado a cada 10 minutos. Se a sessão vencer (você ficou mais de 15 min sem renovar), abra outra com a chave.

GET

/session

Mostra a sessão atual (cliente, escopos, validade). Não conta na cota.

DELETE

/session

Encerra a sessão e libera a chave na hora. Chame ao desligar o seu sistema — assim, se ele subir em outro servidor, não precisa esperar a sessão antiga vencer.

Uma chave, um lugar

Cada chave funciona em um lugar por vez. Isso protege o seu acesso: se a chave vazar, ela não funciona em paralelo em outro sistema.

  • "Lugar" é a rede de onde vêm os pedidos (a mesma faixa de IP, /24 no IPv4 e /48 no IPv6). Trocar de IP dentro da mesma rede, como fazem as nuvens, não é problema.
  • Se você abrir sessão de novo da mesma rede (o seu sistema reiniciou), a sessão anterior é substituída.
  • Se outra rede tentar abrir sessão com a mesma chave enquanto a sua está ativa, recebe 409 key_in_use.
  • Três tentativas assim em 24 horas suspendem a chave por segurança. Fale com a Windwave para liberar.
  • O token de sessão também é preso à rede onde foi aberto: usado de outra rede, responde 401 session_ip_mismatch.

Vai mudar de servidor? Chame DELETE /session no servidor antigo antes, ou espere até 15 minutos para a sessão dele vencer.

Precisa de mais de um lugar (por exemplo, dois servidores em regiões diferentes)? Peça uma chave para cada um, ou uma chave com mais lugares.

Estações de vento

GET

/stations

escopo stations:read

Estações liberadas para a sua chave. live indica se a estação mandou leitura nos últimos 10 minutos.

{ "data": [ {
  "id": "5b1c…", "name": "Cumbuco Norte", "provider": "windwave",
  "spot": { "name": "Cumbuco", "uf": "CE", "lat": -3.6256, "lon": -38.7311 },
  "live": true, "last_reading_at": "2026-11-07T15:12:00Z"
} ] }
GET

/stations/{id}/now

escopo stations:read

A leitura mais recente. age_sec diz há quantos segundos ela foi medida — confira antes de exibir como "agora". Sem leituras ainda, at vem null.

CampoSignificado
wind_ktVento médio, em nós
gust_ktRajada, em nós
dir_degDe onde o vento vem, 0 a 359 (90 = leste)
temp_cTemperatura do ar, °C (pode vir null)

Histórico

GET

/stations/{id}/history?from=&to=&interval=

escopo history:read
ParâmetroPadrãoNotas
from24 h antes de toISO 8601, ex. 2026-11-01T00:00:00Z
toagoraISO 8601
interval1h10m (até 7 dias), 1h (até 31 dias), 1d (até 366 dias)
{ "station_id": "5b1c…", "interval": "1h", "from": "…", "to": "…",
  "data": [ { "t": "2026-11-07T14:00:00Z", "wind_avg_kt": 17.2, "wind_max_kt": 20.1,
              "gust_max_kt": 24.6, "dir_deg": 102, "temp_c": 28.4, "samples": 60 } ] }

t é o início do intervalo. A direção é a média vetorial (não a média simples dos graus). Intervalos sem medição não aparecem.

Câmeras

GET

/cameras

escopo cameras:read

Câmeras liberadas para a sua chave, com online indicando se a imagem está chegando agora.

{ "data": [ { "id": "01d4…", "slug": "canoa-quebrada", "name": "Canoa Quebrada",
  "spot": { "name": "Canoa Quebrada", "uf": "CE", … }, "online": true, "last_seen": "…" } ] }

Vídeo ao vivo

POST

/cameras/{id}/stream

escopo cameras:live

Devolve um endereço de vídeo HLS (.m3u8) que vale 10 minutos. Esse endereço pode ir para o navegador ou app de quem vai assistir — ele não revela a sua chave.

{ "camera_id": "01d4…",
  "hls_url": "https://cams.windwave.com.br/cam/canoa-quebrada/index.m3u8?token=wwst_…",
  "expires_at": "…", "expires_in": 600, "max_viewers": 1 }
  • Telas ao mesmo tempo: max_viewers é quantos aparelhos diferentes (IPs) podem assistir juntos com a sua chave. Passou disso, o vídeo é recusado para o aparelho novo.
  • Validade: quem já está assistindo continua; para começar a assistir depois de 10 minutos, peça um endereço novo.
  • Para exibir numa página, use hls.js (o Safari toca HLS sem biblioteca):
<video id="v" muted autoplay playsinline></video>
<script src="https://cdn.jsdelivr.net/npm/hls.js@1.5.17/dist/hls.min.js"></script>
<script>
  // hlsUrl veio do SEU servidor (que chamou POST /cameras/{id}/stream)
  const v = document.getElementById("v");
  if (v.canPlayType("application/vnd.apple.mpegurl")) v.src = hlsUrl;
  else { const h = new Hls(); h.loadSource(hlsUrl); h.attachMedia(v); }
</script>

O pedido do endereço é feito pelo seu servidor (com a sessão). Nunca coloque a chave nem o token de sessão no navegador.

Uso

GET

/usage

Pedidos do mês, cota, limite por minuto e uso diário dos últimos 30 dias.

GET

/status

sem autenticação

Para o seu monitoramento: responde {"status":"ok"} quando a API está no ar.

Limites

Cada chave tem um limite de pedidos por minuto e uma cota por mês (calendário UTC), definidos no seu contrato. Todo pedido com sessão devolve os cabeçalhos:

CabeçalhoSignificado
RateLimit-LimitPedidos por minuto permitidos
RateLimit-RemainingQuantos ainda cabem neste minuto
RateLimit-ResetSegundos até o minuto virar
X-Quota-Limit / X-Quota-UsedCota do mês e quanto já foi usado

Passou do limite: 429 rate_limited com Retry-After em segundos, ou 429 quota_exceeded até virar o mês.

Dica: para exibir o vento ao vivo, pedir /now a cada 60 segundos por estação é o suficiente. Pedir mais rápido tende a devolver a mesma leitura e só gasta cota.

Erros

Erros seguem o formato RFC 9457 (application/problem+json). Use o campo code no seu sistema; o title é para pessoas.

{ "type": "https://windwave.com.br/developers#erro-key_in_use",
  "title": "Esta chave já está em uso por outro sistema. …",
  "status": 409, "code": "key_in_use", "hint": "…" }
HTTPcodeO que fazer
401missing_tokenEnvie Authorization: Bearer …
401invalid_keyChave errada ou incompleta. Confira se copiou inteira.
401invalid_sessionToken encerrado ou substituído. Abra outra sessão.
401session_expiredPassou de 15 min sem renovar. Abra outra sessão.
401session_ip_mismatchToken usado de outra rede. Abra a sessão de onde vai usar.
409key_in_useA chave tem sessão ativa em outro lugar. Veja Uma chave, um lugar.
403key_suspendedChave suspensa. Fale com a Windwave.
403key_revokedChave cancelada. Use a chave nova.
403key_expiredValidade da chave terminou.
403ip_not_allowedA chave só aceita IPs liberados. Peça para incluir o seu.
403forbiddenO seu plano não inclui este recurso.
404not_foundRecurso não existe ou não está liberado para a chave.
400bad_requestParâmetro inválido; veja detail.
405method_not_allowedMétodo HTTP errado para a rota.
421proxy_requiredUse o endereço oficial windwave.com.br/api/v1.
429rate_limitedEspere Retry-After segundos.
429quota_exceededCota do mês acabou.
429stream_limitLimite de vídeos abertos.
500server_errorTente de novo em alguns segundos, com espera crescente.
503not_configuredAPI em manutenção. Tente mais tarde.

Chave de teste

Chaves ww_test_… seguem as mesmas regras (sessão, limites, erros), mas nunca devolvem dados reais: a lista de estações traz só demo-station, com vento simulado que muda ao longo do dia, e a lista de câmeras vem vazia. Use para integrar antes de contratar.

GET /stations/demo-station/now
GET /stations/demo-station/history?interval=10m

Cliente completo

Um cliente que abre a sessão, renova sozinho, trata os erros mais comuns e libera a chave ao desligar.

// npm não precisa: Node 18+ tem fetch
const BASE = "https://windwave.com.br/api/v1";
const KEY = process.env.WINDWAVE_KEY;          // ww_live_…
let token = null, renewAt = 0;

async function open() {
  const r = await fetch(BASE + "/session", { method: "POST", headers: { Authorization: "Bearer " + KEY } });
  const j = await r.json();
  if (!r.ok) throw Object.assign(new Error(j.title), { code: j.code });
  token = j.token; renewAt = Date.now() + 10 * 60e3;
}
async function refresh() {
  const r = await fetch(BASE + "/session/refresh", { method: "POST", headers: { Authorization: "Bearer " + token } });
  if (!r.ok) return open();                     // venceu? abre outra
  token = (await r.json()).token; renewAt = Date.now() + 10 * 60e3;
}
async function api(path, init = {}) {
  if (!token) await open(); else if (Date.now() > renewAt) await refresh();
  let r = await fetch(BASE + path, { ...init, headers: { ...init.headers, Authorization: "Bearer " + token } });
  if (r.status === 401) { await open(); r = await fetch(BASE + path, { ...init, headers: { ...init.headers, Authorization: "Bearer " + token } }); }
  if (r.status === 429) { const s = +r.headers.get("retry-after") || 60; await new Promise(ok => setTimeout(ok, s * 1000)); return api(path, init); }
  const j = await r.json();
  if (!r.ok) throw Object.assign(new Error(j.title), { code: j.code });
  return j;
}
async function close() { if (token) await fetch(BASE + "/session", { method: "DELETE", headers: { Authorization: "Bearer " + token } }).catch(() => {}); }
process.on("SIGINT", async () => { await close(); process.exit(0); });
process.on("SIGTERM", async () => { await close(); process.exit(0); });

// exemplo: atualiza o vento a cada minuto
const { data } = await api("/stations");
setInterval(async () => {
  for (const s of data) { const now = await api(`/stations/${s.id}/now`); console.log(s.name, now.wind_kt, "kt", now.dir_deg + "°"); }
}, 60e3);

Segurança

  • A chave fica só no servidor. Se ela aparecer em código público, num app ou numa página, peça a troca na hora.
  • A Windwave não guarda a sua chave, só uma impressão digital dela. Perdeu a chave? Pedimos outra; não dá para recuperar a antiga.
  • Na troca de chave, a antiga pode continuar valendo por alguns dias para você atualizar sem parar.
  • Use sempre HTTPS.
  • Se quiser, a chave pode aceitar só IPs ou faixas que você indicar.

Versões

O v1 no endereço só muda se algo deixar de ser compatível. Campos novos podem aparecer nas respostas a qualquer momento — ignore os que você não conhece. A versão exata vem no cabeçalho X-Windwave-Api-Version.

VersãoDataMudanças
1.0.0nov/2026Primeira versão: sessão, estações, histórico, câmeras e vídeo ao vivo.

Especificação OpenAPI 3.1: /developers/openapi.json (importe no Postman, Insomnia ou gere um cliente).