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
- O endereço é o que o dono da API informou (
…gw.nnumbers.com.brou um domínio próprio). - O gateway roteia por nome (
Hoste SNI), não por IP. Chamar pelo IP não funciona. - Autentique com
x-api-key: <chave>(ouOcp-Apim-Subscription-Key) ou comAuthorization: Bearer <JWT>, conforme o modo da API. - Não coloque a chave na URL.
- Trate
429como "reduza a taxa e repita com espera";401como credencial;403como autorização. 404com{"error":"not_found"}significa endereço ou rota errados no gateway — a requisição nem chegou ao backend.5xxcomupstreamno nome é o backend do fornecedor, não o gateway.- SSE e streaming passam sem buffer.
- O gateway acrescenta
X-Request-Id; use-o ao abrir suporte. - 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
| Modo | Cabeçalho |
|---|---|
PUBLIC | nenhum |
API_KEY | x-api-key: nnag_… ou Ocp-Apim-Subscription-Key: nnag_… — só o que a política da API escolheu |
JWT_OIDC | Authorization: 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,EventSourceno 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çalho | Comportamento |
|---|---|
X-Request-Id | o gateway gera (se você não enviar) e devolve na resposta; use ao abrir suporte |
X-Correlation-Id | aceito 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-IP | removidos; o IP real vem da conexão, não do cabeçalho |
__nn_key (query) | removido; não serve para autenticar |
Ocp-Apim-Subscription-Key | autentica só quando a API foi configurada para ele |
traceparent | repassado (sem tracing no produto hoje) |
Códigos e o que fazer
| Status | Significado | Ação |
|---|---|---|
200, 201, 204 | sucesso | — |
400 | requisição inválida (do gateway ou do backend) | corrija o payload |
401 | credencial ausente, inválida ou revogada, ou token inválido | confira a chave ou o token |
403 | autorização (consumidor não autorizado, escopo, organização suspensa) | peça acesso |
404 com {"error":"not_found"} | endereço ou rota inexistente no gateway | confira o endereço publicado |
413 | corpo acima do limite da API | reduza o corpo |
429 | limite de taxa | espere e repita com backoff exponencial |
500, 502, 503, 504 | backend indisponível, lento ou resposta inválida | verifique o backend; guarde o X-Request-Id |
503 sem backend | gateway sem capacidade | tente novamente |
Boas práticas de cliente
- Retentativa: apenas em
429,502,503,504e erro de conexão — e nunca emPOSTsem chave de idempotência do seu domínio. - Backoff exponencial com jitter, respeitando
Retry-Afterquando 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 oX-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
- Segurança e autenticação: o que o gateway valida em cada modo.
- Chaves de API: como obter e rotacionar a credencial.
- Erros e diagnóstico: o lado da administração e o lado do tráfego.
Esta página ajudou?
Reportar um problema nesta páginaNão envie senhas, chaves, tokens ou dados de clientes.