Pular para o conteúdo principal

Como chamar uma API publicada

Esta página é para quem consome uma API publicada no API Gateway. Se você é quem publica, comece por Primeiros passos.

O essencial em dez linhas​

  1. O endereço é o que o dono da API informou (…gw.nnumbers.com.br ou um domínio próprio).
  2. O gateway roteia por nome (Host e SNI), não por IP. Chamar pelo IP não funciona.
  3. Autentique com x-api-key: <chave> (ou Ocp-Apim-Subscription-Key) ou com Authorization: Bearer <JWT>, conforme o modo da API.
  4. Não coloque a chave na URL.
  5. Trate 429 como "reduza a taxa e repita com espera"; 401 como credencial; 403 como autorização.
  6. 404 com {"error":"not_found"} significa endereço ou rota errados no gateway — a requisição nem chegou ao backend.
  7. 5xx com upstream no nome é o backend do fornecedor, não o gateway.
  8. SSE e streaming passam sem buffer.
  9. O gateway acrescenta X-Request-Id; use-o ao abrir suporte.
  10. Cabeçalhos de identidade que você enviar (X-Consumer-*, X-NNumbers-*, X-Forwarded-For, X-Real-IP) são removidos antes de chegar ao backend.

Autenticação​

ModoCabeçalho
PUBLICnenhum
API_KEYx-api-key: nnag_… ou Ocp-Apim-Subscription-Key: nnag_… — só o que a política da API escolheu
JWT_OIDCAuthorization: Bearer <JWT> (assinatura RSA: RS256, RS384 ou RS512)

No modo API_KEY, a chave não chega ao backend (o gateway a remove) e o cabeçalho é exclusivo: mandar x-api-key numa API configurada para Ocp-Apim-Subscription-Key (ou o inverso) resulta em 401, porque o cabeçalho não escolhido é removido antes da autenticação. No modo JWT_OIDC, o token também não é repassado ao backend por padrão.

cURL​

# API com chave
curl -i https://sua-api.gw.nnumbers.com.br/pedidos/42 \
-H 'x-api-key: nnag_EXEMPLO…' \
-H 'Accept: application/json'

# alias compatível com Azure API Management — use quando a API usa esse cabeçalho
curl -i https://sua-api.gw.nnumbers.com.br/pedidos/42 \
-H 'Ocp-Apim-Subscription-Key: nnag_EXEMPLO…'

# POST com corpo
curl -i -X POST https://sua-api.gw.nnumbers.com.br/pedidos \
-H 'x-api-key: nnag_EXEMPLO…' \
-H 'Content-Type: application/json' \
-d '{"cliente":"ACME","total":149.90}'

# API com JWT
curl -i https://sua-api.gw.nnumbers.com.br/pedidos \
-H "Authorization: Bearer $TOKEN"

JavaScript e TypeScript​

const BASE = "https://sua-api.gw.nnumbers.com.br";
const KEY = process.env.API_KEY!; // nunca no código

type Pedido = { id: string; cliente: string; total: number };

async function buscarPedido(id: string): Promise<Pedido> {
const res = await fetch(`${BASE}/pedidos/${encodeURIComponent(id)}`, {
headers: {
"x-api-key": KEY,
Accept: "application/json",
},
});

if (res.status === 429) {
const espera = Number(res.headers.get("Retry-After") ?? "1");
await new Promise((r) => setTimeout(r, espera * 1000));
return buscarPedido(id); // uma retentativa; em produção, limite as tentativas
}
if (res.status === 401) throw new Error("credencial inválida ou revogada");
if (res.status === 403) throw new Error("consumidor sem autorização nesta API");
if (!res.ok) throw new Error(`HTTP ${res.status}: ${await res.text()}`);

return (await res.json()) as Pedido;
}

async function criarPedido(cliente: string, total: number): Promise<Pedido> {
const res = await fetch(`${BASE}/pedidos`, {
method: "POST",
headers: {
"x-api-key": KEY,
"Content-Type": "application/json",
// chave de idempotência do SEU domínio, se a API exigir:
"Idempotency-Key": crypto.randomUUID(),
},
body: JSON.stringify({ cliente, total }),
});
if (!res.ok) throw new Error(`HTTP ${res.status}: ${await res.text()}`);
return (await res.json()) as Pedido;
}

Em Node.js, fetch existe nativamente desde a versão 18. No navegador, a chamada está sujeita a CORS — o dono da API precisa permitir a sua origem.

Python​

import os
import httpx

BASE = "https://sua-api.gw.nnumbers.com.br"
KEY = os.environ["API_KEY"] # nunca no código
HEADERS = {"x-api-key": KEY, "Accept": "application/json"}


def buscar_pedido(pedido_id: str) -> dict:
with httpx.Client(timeout=10.0) as client:
r = client.get(f"{BASE}/pedidos/{pedido_id}", headers=HEADERS)
if r.status_code == 401:
raise RuntimeError("credencial inválida ou revogada")
if r.status_code == 403:
raise RuntimeError("consumidor sem autorização nesta API")
if r.status_code == 429:
raise RuntimeError("limite de taxa excedido; repita com espera")
r.raise_for_status()
return r.json()


def criar_pedido(cliente: str, total: float) -> dict:
with httpx.Client(timeout=10.0) as client:
r = client.post(
f"{BASE}/pedidos",
headers={**HEADERS, "Content-Type": "application/json"},
json={"cliente": cliente, "total": total},
)
r.raise_for_status()
return r.json()

Com a biblioteca requests é idêntico, trocando httpx.Client por requests.Session.

Streaming (SSE) em Python​

import httpx

with httpx.stream("GET", f"{BASE}/eventos", headers=HEADERS, timeout=None) as r:
r.raise_for_status()
for linha in r.iter_lines():
if linha.startswith("data:"):
print(linha[5:].strip())

Java​

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.Duration;

public final class PedidosClient {

private static final String BASE = "https://sua-api.gw.nnumbers.com.br";
private final String apiKey;
private final HttpClient http = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(5))
.build();

public PedidosClient(String apiKey) {
this.apiKey = apiKey; // venha de variável de ambiente ou cofre
}

public String buscar(String pedidoId) throws Exception {
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create(BASE + "/pedidos/" + pedidoId))
.timeout(Duration.ofSeconds(10))
.header("x-api-key", apiKey)
.header("Accept", "application/json")
.GET()
.build();

HttpResponse<String> res = http.send(req, HttpResponse.BodyHandlers.ofString());
return switch (res.statusCode()) {
case 200 -> res.body();
case 401 -> throw new IllegalStateException("credencial inválida ou revogada");
case 403 -> throw new IllegalStateException("consumidor sem autorização nesta API");
case 429 -> throw new IllegalStateException("limite excedido; repetir com espera");
default -> throw new IllegalStateException("HTTP " + res.statusCode() + ": " + res.body());
};
}
}

Para POST, use .header("Content-Type", "application/json").POST(HttpRequest.BodyPublishers.ofString(json)).

Server-Sent Events​

O gateway não faz buffer da resposta: um text/event-stream atravessa à medida que o backend produz os eventos. Requisitos do lado do consumidor:

  • use um cliente que não acumule a resposta inteira (curl -N, httpx.stream, EventSource no navegador, BodyHandlers.ofLines() no Java);
  • não defina timeout curto de leitura — conexões SSE duram;
  • o corpo da requisição não é inspecionado nem tamponado pelo gateway.
curl -N -H 'x-api-key: nnag_EXEMPLO…' https://sua-api.gw.nnumbers.com.br/eventos

O tempo máximo de vida de uma conexão SSE é o do balanceador e o do seu backend; o produto não impõe um limite próprio documentado. WebSocket também atravessa, mas não há teste dedicado a ele nesta versão.

Cabeçalhos que o gateway trata​

CabeçalhoComportamento
X-Request-Ido gateway gera (se você não enviar) e devolve na resposta; use ao abrir suporte
X-Correlation-Idaceito se for token seguro (até 128 caracteres); devolvido na resposta
X-Consumer-*, X-Credential-Identifier, X-NNumbers-*removidos do que você enviar
Forwarded, X-Forwarded-*, X-Real-IPremovidos; o IP real vem da conexão, não do cabeçalho
__nn_key (query)removido; não serve para autenticar
Ocp-Apim-Subscription-Keyautentica só quando a API foi configurada para ele
traceparentrepassado (sem tracing no produto hoje)

Códigos e o que fazer​

StatusSignificadoAção
200, 201, 204sucesso—
400requisição inválida (do gateway ou do backend)corrija o payload
401credencial ausente, inválida ou revogada, ou token inválidoconfira a chave ou o token
403autorização (consumidor não autorizado, escopo, organização suspensa)peça acesso
404 com {"error":"not_found"}endereço ou rota inexistente no gatewayconfira o endereço publicado
413corpo acima do limite da APIreduza o corpo
429limite de taxaespere e repita com backoff exponencial
500, 502, 503, 504backend indisponível, lento ou resposta inválidaverifique o backend; guarde o X-Request-Id
503 sem backendgateway sem capacidadetente novamente

Boas práticas de cliente​

  • Retentativa: apenas em 429, 502, 503, 504 e erro de conexão — e nunca em POST sem chave de idempotência do seu domínio.
  • Backoff exponencial com jitter, respeitando Retry-After quando existir.
  • Timeouts explícitos em todas as chamadas; sem isso, uma conexão pendurada derruba seu pool.
  • Reaproveite conexões (keep-alive): abrir TLS por requisição custa caro.
  • Nunca logue a chave nem o Authorization. Logue o X-Request-Id.
  • Não presuma o formato do corpo de erro do gateway: ele varia entre modo de autenticação e erro de plataforma. Decida pelo status.

Próximos passos​