Back to pairl.dev

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.

Step 1

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_....

Step 2

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.

Base URL
https://api.pairl.dev/v1/proxy/anthropic
Python · Anthropic
# 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
    }
)
Base URL
https://api.pairl.dev/v1/proxy/openai
Python · OpenAI
# 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
    }
)
Base URL
https://api.pairl.dev/v1/proxy/google
Python · Google
# 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},
    }
)
Anthropic
curl · Anthropic
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!"}]}'
OpenAI
curl · OpenAI
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!"}]}'
Google
curl · Google
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}}'
Step 3

Check your savings in the response headers

Every response includes headers showing exactly what was saved and what PAIRL charged.

Response headers
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 header
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
Example header
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.

Example request
{ "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

Compressed 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

Comparison
#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

Compressed turns
#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=cond is 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.

Supported Anthropic — api.pairl.dev/v1/proxy/anthropic/...
Supported OpenAI — api.pairl.dev/v1/proxy/openai/...
Supported Google — 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
Google /v1beta/models/{model}:generateContent Full Compression active, native Gemini format
Google /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 · OpenAI Responses
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 · Google
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

Endpoint
POST https://api.pairl.dev/v1/proxy/{provider}/{path}
ParameterDescription
provider anthropic, openai, or google
path The provider's original endpoint path, e.g. v1/messages

Request Headers

HeaderRequiredDescription
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.

GET /v1/usage/summary

Aggregated totals for a time range.

Request
GET /v1/usage/summary?from=2026-02-01&to=2026-02-28
GET /v1/usage/requests/{request_id}

Details for a single request. Use the X-PAIRL-Request-Id header value from any proxied response.

GET /v1/usage/export

Export raw usage records for a time range.

Request
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.

GET /v1/billing/balance

Returns your current credit balance in USD.

GET /v1/billing/packages

Returns available credit packages and prices.

POST /v1/billing/checkout

Creates a Stripe Checkout session. Returns a checkout_url to redirect your user to.

Request body
{ "package_id": "credits_50" }
GET /v1/billing/transactions

Paginated transaction history — top-ups and per-request fee deductions.

Config API

Adjust gateway behaviour for your account without redeployment.

GET /v1/config
PATCH /v1/config
FieldValuesDescription
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.

1

PAIRL compresses your request and forwards the smaller version to your provider. You pay your provider for the compressed token count.

2

PAIRL measures how many tokens were saved and calculates the dollar value at your provider's input token price.

3

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.

PackagePriceCredits
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.