PAIRL Gateway Docs
PAIRL Gateway is a proxy that sits between your application and your AI provider. It compresses context automatically, measures the savings per request, and charges only a fraction of what you save.
Quick Start
Get up and running in three steps — no SDK, no refactoring.
Create an account and get your API key
Sign up at console.pairl.dev. Generate an API key under Settings → API Keys. Keys look like pk_live_....
Change your base URL
This is the only code change required. Pass your PAIRL key as Authorization: Bearer and your provider key via X-Provider-Key.
# pip install anthropic client = Anthropic( base_url="https://api.pairl.dev/v1/proxy/anthropic", api_key="sk-ant-...", # your Anthropic key — unchanged default_headers={ "Authorization": "Bearer pk_live_...", # your PAIRL key } )
# pip install openai client = OpenAI( base_url="https://api.pairl.dev/v1/proxy/openai", api_key="pk_live_...", # your PAIRL key goes here for OpenAI SDK default_headers={ "X-Provider-Key": "sk-...", # your OpenAI key } )
# pip install requests response = requests.post( "https://api.pairl.dev/v1/proxy/google/v1beta/models/gemini-2.5-flash:generateContent", headers={ "Authorization": "Bearer pk_live_...", # your PAIRL key "X-Provider-Key": "AIza...", # your Google API key "Content-Type": "application/json", }, json={ "contents": [{"role": "user", "parts": [{"text": "Hello!"}]}], "generationConfig": {"maxOutputTokens": 1024}, } )
curl https://api.pairl.dev/v1/proxy/anthropic/v1/messages \ -H "Authorization: Bearer pk_live_..." \ -H "X-Provider-Key: sk-ant-..." \ -H "Content-Type: application/json" \ -d '{"model":"claude-opus-4-20250514","max_tokens":1024,"messages":[{"role":"user","content":"Hello!"}]}'
curl https://api.pairl.dev/v1/proxy/openai/v1/chat/completions \ -H "Authorization: Bearer pk_live_..." \ -H "X-Provider-Key: sk-..." \ -H "Content-Type: application/json" \ -d '{"model":"gpt-4o","messages":[{"role":"user","content":"Hello!"}]}'
curl https://api.pairl.dev/v1/proxy/google/v1beta/models/gemini-2.5-flash:generateContent \ -H "Authorization: Bearer pk_live_..." \ -H "X-Provider-Key: AIza..." \ -H "Content-Type: application/json" \ -d '{"contents":[{"role":"user","parts":[{"text":"Hello!"}]}],"generationConfig":{"maxOutputTokens":1024}}'
Check your savings in the response headers
Every response includes headers showing exactly what was saved and what PAIRL charged.
X-PAIRL-Savings-Tokens: 3840 X-PAIRL-Savings-USD: 0.01152 X-PAIRL-Fee-USD: 0.00346 X-PAIRL-Degraded: false
Authentication
Every request to the PAIRL Gateway requires a PAIRL API key passed as a Bearer token.
Authorization: Bearer pk_live_...
Manage your keys at console.pairl.dev → Settings → API Keys. Keys can be created, listed, and revoked at any time.
Your provider key is never stored by PAIRL. Pass it per-request via the appropriate header. PAIRL reads it to forward the request and discards it immediately.
Compression Modes
Control compression per request using the X-PAIRL-Mode header.
| Mode | Behavior | When to use |
|---|---|---|
auto default |
Compress when profitable, pass through when not | All production traffic — recommended default |
force |
Always compress, regardless of input length | Benchmarking and testing compression on specific inputs |
off |
Pure passthrough — no compression, no PAIRL fee | Capturing baselines, debugging, or short single-turn calls |
X-PAIRL-Mode: auto
Streaming
Streaming works exactly as it does without the gateway. Set stream: true as usual: the request is compressed, and the model's response is streamed straight back to your client over SSE, token by token. No client changes are needed — an agent loop that already streams keeps streaming.
First-token latency
Compression runs before the request is forwarded, so the first token is preceded by a short encoding step (the connection is held open in the meantime). On maintained multi-turn conversations this is bounded to encoding only the newest turn — the rest of the history is served from an already-encoded, cache-stable prefix — so per-turn overhead stays small and roughly constant as the conversation grows.
{ "model": "…", "messages": [ … ], "stream": true }
Tool-Use Compression v1.2
PAIRL v1.2 adds native compression for agentic and tool-use conversations — the highest-volume context in Claude Code, Cursor, and similar workflows. Instead of passing raw tool-call/result chains through unchanged, the gateway compresses them into four compact record types.
New Record Types
| Record | Purpose | Required Keys |
|---|---|---|
#call |
Completed tool invocation | tool= |
#ret |
Compressed tool result | call= status=ok|err |
#think |
Summarized reasoning step | summary="..." |
#edit |
Aggregated file edits | file= changes= |
Example: compressed 20-turn session
upd{t=proxy_fix,s=t,l=2,m=+,a=i} @rid=a1
#fact task="fix SSE header stripping" @rid=f1
#fact status=completed @rid=f2
#think summary="need to find proxy implementation" @rid=t01
#call tool=Grep pattern="handleProxy" path="/src/" @rid=c01
#ret call=c01 status=ok matches=3 files="proxy.ts:161,proxy.ts:234,app.ts:558" @rid=r01
#call tool=Read file="/src/proxy.ts" @rid=c02
#ret call=c02 status=ok lines=450 sig="proxy handler with SSE support" @rid=r02
#think summary="SSE headers stripped by content-encoding logic" @rid=t02
#edit file="/src/proxy.ts" changes=2 summary="fixed SSE header stripping" @rid=d01
#call tool=Bash cmd="npm test" @rid=c03
#ret call=c03 status=ok summary="42 passed, 0 failed" exit=0 @rid=r03
#cost val=0.03 cur=USD model=claude-opus-4 @rid=k1
~95% token reduction for tool-call/result chains. A typical 20-turn Claude Code session (~15,000 tokens) compresses to ~800 tokens, while preserving all file signatures, match locations, and reasoning steps needed to continue the task.
Recency window
The last W tool interactions (default W=3) are preserved verbatim so the model retains full detail for its most recent context. Older interactions are compressed to #call/#ret records automatically.
Columnar Record Blocks v1.5
When several records of the same type share a key schema, repeating every key= on each line is pure overhead. A columnar block declares the keys once in a header and lists the values positionally — one row per record.
Verbose vs. columnar
#evid claim="LLM costs decreased 60% in 2025" src=s1 conf=0.85 #evid claim="Multi-agent adoption rose 300%" src=s2 conf=0.90 #evid claim="Transformer alternatives showing promise" src=s3 conf=0.70 # ↓ same data, columnar #evid[claim,src,conf] "LLM costs decreased 60% in 2025" s1 0.85 "Multi-agent adoption rose 300%" s2 0.90 "Transformer alternatives showing promise" s3 0.70
~40% fewer tokens on schema-heavy messages, fully lossless. Columnar rows expand back to key=value records before hashing, so message integrity and dedup are unaffected.
Rules
- A field containing spaces or special characters must be
"quoted"; each row must have exactly as many fields as the header has columns. - The block ends at the next line starting with
#or a blank line. - Applies to fixed-schema records (
#evid,#quota,#cost, tool records). Not used for#fact/#ref, whose key is itself data.
Extractive Quotes v1.6
Compressed history now quotes your conversation instead of retelling it. Each turn is carried as word-for-word excerpts of what was actually said — [...] marks omitted text. Nothing between the quotes is written by a model.
What it looks like
#u1 #req content="API latency spiked to 800ms P99 after the Redis upgrade. [...] connection pool exhaustion starting at 14:32 UTC." @rid=q1 #a2 #rpt content="Redis 7.2 lowered the default from 10000 to 4096. [...] stuck in CLOSE_WAIT you could hit the limit." @rid=q2
Nothing is invented. A model proposes which passages matter, but every quoted passage is checked against the original turn and copied from it character-for-character. A passage that can't be found in the original is dropped — never "fixed" by writing new text.
Reading compressed history
- An unmarked
#req(user turn) or#rpt(assistant turn) is a quote — its wording is evidence of what was said. - A record marked
mode=condis a labeled summary — useful, but a retelling, and always recognizable as one. - Short turns are always carried in full, uncut.
Response Headers
Every proxied response includes these headers so you can observe the impact of each request.
| Header | Type | Description |
|---|---|---|
X-PAIRL-Savings-Tokens |
integer | Tokens removed from the request before forwarding to your provider |
X-PAIRL-Savings-USD |
decimal | Dollar value of tokens saved, based on your provider's input token price |
X-PAIRL-Fee-USD |
decimal | PAIRL fee charged for this request. Zero if no savings occurred or if degraded |
X-PAIRL-Degraded |
boolean | True if compression failed and the request was passed through unchanged. Fee is always zero when degraded |
X-PAIRL-Request-Id |
string | Unique ID for this request, usable with the Usage API |
Supported Providers
PAIRL supports Anthropic, OpenAI, and Google. Route to any of them by changing the provider segment of the proxy URL.
api.pairl.dev/v1/proxy/anthropic/...
api.pairl.dev/v1/proxy/openai/...
api.pairl.dev/v1/proxy/google/...
Endpoint compatibility
| Provider | Endpoint | Status | Notes |
|---|---|---|---|
| Anthropic | /v1/messages |
Full | Compression active |
| Anthropic | /v1/messages (streaming) |
Passthrough | Forwarded, no compression |
| OpenAI | /v1/chat/completions |
Full | Compression active |
| OpenAI | /v1/chat/completions (streaming) |
Passthrough | Forwarded, no compression |
| OpenAI | /v1/responses |
Full | Compression active — required for GPT-5.6+ tool use with reasoning |
| OpenAI | /v1/responses (streaming) |
Passthrough | Forwarded, no compression |
| OpenAI | /v1/embeddings |
Passthrough | No compression benefit |
/v1beta/models/{model}:generateContent |
Full | Compression active, native Gemini format | |
/v1beta/models/{model}:generateContent (streaming) |
Passthrough | Forwarded, no compression |
OpenAI — Responses API
The GPT-5.6 family requires the Responses API (/v1/responses) for tool calling with reasoning enabled — /v1/chat/completions rejects that combination. PAIRL supports it natively: same base URL, keep the /responses path. The gateway compresses the input and preserves tools, instructions and reasoning settings — no client changes beyond pointing the base URL at PAIRL.
curl https://api.pairl.dev/v1/proxy/openai/v1/responses \ -H "Authorization: Bearer pk_live_..." \ -H "X-Provider-Key: sk-..." \ -H "Content-Type: application/json" \ -d '{ "model": "gpt-5.6", "instructions": "You are a helpful assistant.", "input": [{"role": "user", "content": "Hello!"}] }'
With the OpenAI SDK, keep base_url=".../v1/proxy/openai" and call client.responses.create(...) exactly as you would against OpenAI directly.
Google — API format
The Google provider uses the native Gemini REST API format. Pass your Google API key via X-Provider-Key and your PAIRL key as Authorization: Bearer.
curl https://api.pairl.dev/v1/proxy/google/v1beta/models/gemini-2.5-flash:generateContent \ -H "Authorization: Bearer pk_live_..." \ -H "X-Provider-Key: AIza..." \ -H "Content-Type: application/json" \ -d '{ "contents": [{"role": "user", "parts": [{"text": "Hello!"}]}], "generationConfig": {"maxOutputTokens": 1024} }'
Supported models include gemini-2.5-pro, gemini-2.5-flash, gemini-2.0-flash, and gemini-2.0-flash-lite.
Proxy Endpoint
POST https://api.pairl.dev/v1/proxy/{provider}/{path}
| Parameter | Description |
|---|---|
provider |
anthropic, openai, or google |
path |
The provider's original endpoint path, e.g. v1/messages |
Request Headers
| Header | Required | Description |
|---|---|---|
Authorization |
Required | Bearer pk_live_... — your PAIRL API key |
X-PAIRL-Mode |
Optional | auto (default), force, or off |
Idempotency-Key |
Optional | Deduplicates requests on retry |
Usage API
Query your token savings and cost data programmatically. All endpoints require a key with usage:read scope.
/v1/usage/summary
Aggregated totals for a time range.
GET /v1/usage/summary?from=2026-02-01&to=2026-02-28
/v1/usage/requests/{request_id}
Details for a single request. Use the X-PAIRL-Request-Id header value from any proxied response.
/v1/usage/export
Export raw usage records for a time range.
GET /v1/usage/export?from=2026-02-01&to=2026-02-28&format=csv GET /v1/usage/export?from=2026-02-01&to=2026-02-28&format=json
Billing API
Check your credit balance, view transaction history, and purchase credit top-ups programmatically.
/v1/billing/balance
Returns your current credit balance in USD.
/v1/billing/packages
Returns available credit packages and prices.
/v1/billing/checkout
Creates a Stripe Checkout session. Returns a checkout_url to redirect your user to.
{ "package_id": "credits_50" }
/v1/billing/transactions
Paginated transaction history — top-ups and per-request fee deductions.
Config API
Adjust gateway behaviour for your account without redeployment.
/v1/config
/v1/config
| Field | Values | Description |
|---|---|---|
payload_persistence |
off · debug_ttl |
Whether request payloads are temporarily stored for debugging. Default: off |
debug_ttl_hours |
integer | How long debug payloads are retained before automatic deletion |
failure_mode |
fail_open · fail_closed |
What happens if compression fails. fail_open passes the request through unchanged (default) |
How Billing Works
PAIRL uses a savings-share model. You pay your AI provider directly (BYOK). PAIRL charges only a fraction of the savings it delivers on each request.
PAIRL compresses your request and forwards the smaller version to your provider. You pay your provider for the compressed token count.
PAIRL measures how many tokens were saved and calculates the dollar value at your provider's input token price.
A percentage of that saving (your plan's savings share) is deducted from your credit balance as the PAIRL fee. If there are no savings — or if the request degraded — the fee is zero.
You always keep the majority of every dollar saved. Even on the Basic plan, PAIRL takes 30% and you keep 70% of the gross saving.
Credits
PAIRL fees are deducted from a prepaid credit balance. Top up at any time from the dashboard or via the Billing API.
| Package | Price | Credits |
|---|---|---|
| Starter | €10 | $10 USD credit |
| Standard | €50 | $50 USD credit |
| Professional | €100 | $100 USD credit |
| Enterprise | €500 | $500 USD credit |
Heads up: If your credit balance falls below −$5, subsequent requests will return a 402 Payment Required response. Top up manually or enable auto-refill under Settings → Billing to avoid interruptions.
Privacy
PAIRL is designed with privacy-by-default. Your prompts and completions are never stored.
What is stored
- + Request ID, timestamp, and API key ID
- + Provider and model name
- + Token counts (original and compressed)
- + Savings amount and PAIRL fee
- + Degraded flag and compression mode used
What is never stored
- − The content of your prompts or messages
- − Model completions or responses
- − Your provider API keys
Optional debug retention is available via the Config API (payload_persistence: debug_ttl). When enabled, payloads are stored encrypted with a short TTL you configure, then automatically deleted.
Known Limitations
Short inputs
Compression is most effective on long message histories and multi-turn conversations. Very short inputs (under ~50 tokens) are automatically passed through in auto mode with no savings and no fee.
Provider coverage
Anthropic, OpenAI, and Google are currently supported. Requests to other providers return a 400 unsupported_provider error. Additional providers are on the roadmap.