OpenAI-Compatible AI Gateway
Punk is an OpenAI-compatible AI gateway for production agents. If an app already calls client.chat.completions.create(...), the first integration is a base URL change: send the same request shape through Punk, add identity headers, and inspect the route explanation that comes back with every run.
Use this path when you want agent observability, exact caching, governance, trace-based learning, provider failover, and route explanations without changing the rest of the application.
Install
You do not need the Punk SDK for gateway-only traffic. Keep using the official OpenAI client:
npm install openai
Create a Punk tenant API key in the hosted dashboard, then point the OpenAI client at Punk:
export PUNK_BASE_URL=https://app.punktechnologies.com
export PUNK_API_KEY=pk_...
Provider keys and BYOK credentials live in Punk's hosted control plane, not in this app.
For production-like enterprise traffic, start with an observe-mode key and use Enterprise Pilot for the pilot sequence.
First-Run Checklist
- Set the OpenAI client base URL to
$PUNK_BASE_URL/v1. - Use the Punk tenant API key as the OpenAI client key or bearer token.
- Send
X-Punk-App,X-Punk-Agent, andX-Punk-Subject. - Send one deterministic request and record
x-punk-run-idplusx-punk-route. - Open the run explanation. Check readiness separately with an admin key when you are validating deployment posture.
Working curl smoke test:
curl -sS -D /tmp/punk-headers -o /tmp/punk-body \
"$PUNK_BASE_URL/v1/chat/completions" \
-H "Authorization: Bearer $PUNK_API_KEY" \
-H "Content-Type: application/json" \
-H "X-Punk-App: support-app" \
-H "X-Punk-Agent: support-agent" \
-H "X-Punk-Subject: user-123" \
-d '{
"model": "gpt-4o-mini",
"temperature": 0,
"messages": [
{ "role": "user", "content": "Classify this ticket: I was charged twice. Return JSON." }
]
}'
RUN_ID=$(awk 'tolower($1)=="x-punk-run-id:" {print $2}' /tmp/punk-headers | tr -d '\r' | tail -n 1)
ROUTE=$(awk 'tolower($1)=="x-punk-route:" {print $2}' /tmp/punk-headers | tr -d '\r' | tail -n 1)
jq -r '.choices[0].message.content' /tmp/punk-body
printf 'route=%s runId=%s\n' "$ROUTE" "$RUN_ID"
curl -sS -H "Authorization: Bearer $PUNK_API_KEY" \
"$PUNK_BASE_URL/api/v1/runs/$RUN_ID" \
| jq '{route: .run.route, provider: .run.provider, explanation: .run.routeExplanation}'
Admin readiness check:
curl -sS -H "Authorization: Bearer ${PUNK_ADMIN_API_KEY:-$PUNK_API_KEY}" \
"$PUNK_BASE_URL/api/v1/readiness" \
| jq '{summary, attention: [.items[]? | select(.status=="attention") | {id, message, action}]}'
The first request normally routes live. Repeat the exact same safe request to test exact_cache. In observe mode, Punk returns the live provider response and records would-have optimization as ghost evidence in the run explanation. Artifact and model-substitution routes require repeated stable traffic, replay/shadow evidence, and promotion.
One-Line Integration
import OpenAI from "openai";
const punkBaseUrl = process.env.PUNK_BASE_URL ?? "https://app.punktechnologies.com";
const client = new OpenAI({
baseURL: `${punkBaseUrl.replace(/\/$/, "")}/v1`,
apiKey: process.env.PUNK_API_KEY,
defaultHeaders: {
"X-Punk-App": "support-app",
"X-Punk-Agent": "support-agent",
"X-Punk-Subject": "user-123"
}
});
const result = await client.chat.completions.create({
model: "gpt-4o-mini",
temperature: 0,
messages: [
{ role: "user", content: "Classify this ticket: I was charged twice." }
]
});
console.log(result.choices[0]?.message?.content);
Everything above the client constructor stays OpenAI-shaped. Streaming, tools, response_format, and normal model parameters continue to use the OpenAI client surface.
What Punk Adds
| Capability | What happens after the base URL change |
|---|---|
| Trace | Every model request becomes a run with cost, latency, input, output, identity, and route evidence. |
| Cache | Repeated safe work can route through exact cache before touching the provider again. |
| Govern | Tenant policy can block, require approval, redact, or allow requests based on identity and side-effect posture. |
| Learn | Stable repeated patterns can become candidate deterministic artifacts after replay and shadow evidence. |
| Explain | Responses include x-punk-run-id and x-punk-route; run detail explains why that route won. |
Read The Route
The OpenAI SDK exposes response headers through .withResponse():
const { data, response } = await client.chat.completions
.create({
model: "gpt-4o-mini",
messages: [{ role: "user", content: "Say hello." }]
})
.withResponse();
console.log(data.choices[0]?.message?.content);
console.log(response.headers.get("x-punk-route"));
console.log(response.headers.get("x-punk-run-id"));
Then inspect the run:
curl -sS -H "Authorization: Bearer $PUNK_API_KEY" \
"$PUNK_BASE_URL/api/v1/runs/<runId>" \
| jq .run.routeExplanation
First-Run Troubleshooting
| Symptom | Check | Fix |
|---|---|---|
| No runs appear | Response lacks x-punk-run-id, or /api/v1/runs?limit=5 is empty. | Confirm the client base URL ends in /v1, the request goes to Punk, and the key is a Punk key. |
| Missing identity | Run detail shows default-app, anonymous, or an unexpected app. | Send all three X-Punk-* headers. If the API key is app-pinned, its app overrides X-Punk-App. |
| Auth failure | 401 means missing/invalid bearer token; /api/v1/readiness can return 403. | Use Authorization: Bearer <Punk tenant key>. Use an admin key for readiness. |
| Mock vs live confusion | Run provider is mock, or readiness says only mock is configured. | Add a live provider/BYOK key in Punk. Local PUNK_PROVIDER=mock deliberately forces deterministic simulation. |
| No optimization yet | Route is live; alternatives say cache miss, no pattern, or gate not satisfied. | Repeat stable requests. Observe-mode keys record ghost savings; artifacts serve only after replay, shadow, and promotion evidence. |
When To Add The SDK
The gateway sees model traffic. Add @punktechnologies/sdk when you also want Punk to see application context the model request does not include:
| Need | SDK feature |
|---|---|
| Trace tools | traceTool(...) records tool calls and side-effect levels. |
| Cache read-only tools | traceTool({ sideEffectLevel: 1, ttlSeconds: 300, ... }) participates in the tool-result cache. |
| Send feedback | feedback(runId, rating, correction) closes the learning loop. |
| Read evidence | runDetail, receipt, and evidencePacket return route and audit material. |
| Fetch web context | fetchSom(url) returns compact semantic page context instead of raw HTML. |
Read next: SDK, Agent Observability & Tool Caching, API.
For OpenRouter provider/model slugs through the same OpenAI-compatible wire, see OpenRouter.