API Documentation

Parity Layer API

Parity Layer is a drop-in AI gateway. It proves a cheaper model produces output that matches or beats your expensive model on your actual prompts, then routes to it automatically. You cut AI API costs by 30-60% with output quality preserved and instant fallback. Integration is a two-line change to any OpenAI or Anthropic SDK.

Base URL & authentication

Send requests to https://api.paritylayer.com (use /v1 for OpenAI-style clients). Authenticate with your Parity key (sk-pl-...), which you generate in the dashboard. Your own provider key stays in Parity so it can talk to OpenAI or Anthropic on your behalf.

Authorization: Bearer sk-pl-your-parity-key

Quickstart

Keep your existing code. Change the base URL and the key, nothing else. Prompts, tools, streaming, parameters, and response shapes are identical to calling the provider directly.

Python, OpenAI SDK

from openai import OpenAI

client = OpenAI(
 base_url="https://api.paritylayer.com/v1",
 api_key="sk-pl-...", # your Parity key
)

resp = client.chat.completions.create(
 model="gpt-4o", # your current baseline model
 messages=[{"role": "user", "content": "Summarize this ticket..."}],
)

Python, Anthropic SDK

import anthropic

client = anthropic.Anthropic(
 base_url="https://api.paritylayer.com",
 api_key="sk-pl-...", # your Parity key
)

msg = client.messages.create(
 model="claude-sonnet-4-20250514",
 max_tokens=1024,
 messages=[{"role": "user", "content": "Extract the fields as JSON..."}],
)

TypeScript, OpenAI SDK

import OpenAI from "openai";

const client = new OpenAI({
 baseURL: "https://api.paritylayer.com/v1",
 apiKey: "sk-pl-...",
});

const resp = await client.chat.completions.create({
 model: "gpt-4o",
 messages: [{ role: "user", content: "Classify this message..." }],
});

cURL

curl https://api.paritylayer.com/v1/chat/completions \
 -H "Authorization: Bearer sk-pl-..." \
 -H "Content-Type: application/json" \
 -d '{
 "model": "gpt-4o",
 "messages": [{"role": "user", "content": "Hello"}]
 }'

Endpoints

Parity Layer mirrors the upstream provider APIs, so you use the endpoint your SDK already calls:

Streaming (stream: true), tool / function calling, system prompts, and every parameter you already pass are supported and behave exactly as they do upstream. Each endpoint returns the identical response shape as the provider, so your parsing code does not change.

How routing works

You keep calling your baseline model. In the background, Parity tests cheaper candidate models on your actual prompts and statistically proves whether a candidate matches, or beats, your baseline for that prompt type. Only once a candidate is proven does Parity route that prompt type to it. If quality ever drifts, it falls back to your baseline model instantly, so your users never see a worse answer. You stay in control of when routing activates.

Offline proof (no code change)

Not ready to route live traffic? Upload a sample of past requests as a JSONL export and Parity proves the achievable savings offline, before you change a single line of code. You see the number for your own workload first, then decide.

The fastest way to produce the file: paste the prompt below into Claude Code (or any coding agent) inside your codebase, or hand it to a developer. It instruments your app to log 100 request/response pairs per prompt to parity-capture.jsonl, without touching your live traffic. Run it for about a day, then upload the file in the dashboard under Offline import.

You are instrumenting this codebase to produce a capture file for Parity Layer
(paritylayer.com) — WITHOUT routing any traffic through a third party. Parity
Layer uses the file to prove a cheaper model matches your current model's
output quality on your real prompts, then projects your savings.

GOAL: one file, ./parity-capture.jsonl, holding 100 real request/response
pairs for EACH distinct prompt this app runs, plus true call volume.

Do exactly this:

1. Find every place this app calls an LLM provider (OpenAI, Anthropic, or an
   OpenAI-compatible API).

2. Give each distinct logical prompt a short stable id — its "tag" (e.g.
   "summarize-ticket"). Every call running the same logical prompt must log
   the SAME tag. Use a tag for ANY prompt that may run more than 100 times
   during the capture window — the volume summary in step 6 only works for
   tagged prompts. If a low-traffic prompt has no natural id, omit the tag
   (Parity Layer will fingerprint it from the message content) and do NOT
   cap it — log every call.

3. Wrap each call so that, immediately after the provider responds, you append
   ONE JSON object per line (JSONL) to ./parity-capture.jsonl:
   - "tag": the stable prompt id from step 2
   - "timestamp": ISO-8601 UTC time of the call — REQUIRED (used to project
     your true monthly volume)
   - "model": the exact model string called (e.g. "gpt-4o")
   - "provider": "openai" | "anthropic" | ...
   - "request_body": the RAW provider request object. It MUST contain a
     non-empty "messages" array. If a call does not use chat-messages shape
     (e.g. the OpenAI Responses API), convert it faithfully to
     {"messages": [{"role": ..., "content": ...}]} when logging.
   - "response_body": the RAW provider response object in its FINAL,
     non-streaming shape (an OpenAI chat completion or an Anthropic message).
     Do not truncate or reshape it. For streamed calls, assemble the complete
     final response (full content + usage) and log that.
   - "cost_usd": what this call cost you — REQUIRED for the savings
     projection. If you do not already track per-call cost, compute it from
     the response's token counts x the provider's current list prices for
     that model.
   - "prompt_tokens" and "completion_tokens": from the response's usage.
   - Optional: "latency_ms", "cache_read_tokens", "cache_creation_tokens",
     "cache_hit".
   OMIT any field you do not have a value for — never write null.
   Logging is append-only and must NEVER block, slow, or alter the real call.
   If logging fails, the app must behave exactly as before.

4. Only log (and count) a line if the response contains a real assistant
   message — non-empty text content or tool calls. Never log provider
   ERROR responses as capture lines.

5. Stop at 100 logged lines per tag — then keep counting. Parity Layer proves
   each prompt on its first 100 captures. After a tag reaches 100 full lines,
   log no more full lines for it; count every further call instead.

6. Run for AT LEAST 24 hours of wall-clock time under normal production
   traffic, even if every tag reaches its 100 much sooner — the savings
   projection is computed from the capture window and needs a full daily
   cycle. Keep counting calls until the window ends.

7. When the capture window ends, append ONE summary line for EVERY tag —
   not only the busy ones — using the same tag as its logged lines and the
   full window:
   {"type":"volume_summary","tag":"<id>",
    "observed_requests":<total calls for this tag in the window, including
      the logged ones>,
    "window_start":"<iso start of the whole capture window>",
    "window_end":"<iso end of the whole capture window>",
    "per_model":[{"model":"...","count":N,"avg_prompt_tokens":...,
      "avg_completion_tokens":...,"avg_baseline_cost_usd":...,
      "cache_hit_rate":...}]}
   Omit any per_model number you do not have — never write null.
   Low-traffic prompts that never reach 100 in the window are fine — log
   whatever happens.

8. NEVER log secrets, API keys, or auth headers — only the fields above.

When the file is ready, upload ./parity-capture.jsonl in the Parity Layer
dashboard under "Offline import". Parity Layer proves parity from it and
shows your projected savings. Nothing from your side is sent anywhere except
this one file you upload.

The file holds only request and response content plus token counts, never API keys or credentials, and it stays on your machine until you choose to upload it.

For OpenRouter users

If your traffic runs through OpenRouter, use this version instead. It logs the model that actually served each request (OpenRouter can route one prompt to different models) and takes the exact per-request cost straight from the response, so the capture works no matter how your OpenRouter account is configured or billed.

You are instrumenting this codebase to produce a capture file for Parity Layer
(paritylayer.com) — WITHOUT routing any traffic through a third party. This
app calls its LLMs through OpenRouter (openrouter.ai). Parity Layer uses the
file to prove a cheaper model matches your current model's output quality on
your real prompts, then projects your savings.

GOAL: one file, ./parity-capture.jsonl, holding 100 real request/response
pairs for each prompt this app runs, plus true call volume.

Do exactly this:

1. Find every place this app calls an LLM through OpenRouter (directly, or
   via an OpenAI-compatible SDK pointed at openrouter.ai).

2. Give each distinct logical prompt a short stable id — its "tag" (e.g.
   "summarize-ticket"). A tag is REQUIRED on every logged line, and every
   call running the same logical prompt must log the SAME tag.

3. Wrap each call so that, immediately after OpenRouter responds, you append
   ONE JSON object per line (JSONL) to ./parity-capture.jsonl. Take every
   field from the response itself — do not assume how OpenRouter is
   configured or billed:
   - "tag": the stable prompt id from step 2 — REQUIRED
   - "timestamp": ISO-8601 UTC time of the call — REQUIRED
   - "model": the RESPONSE body's "model" field, EXACTLY as returned — this
     is the model that actually served the request. NEVER log the requested
     model string: with OpenRouter routing (openrouter/auto, fallback
     lists, provider preferences) the requested and served model can
     differ. Do not strip or rewrite the slug — keep any ":free" suffix.
   - "provider": the literal string "openrouter"
   - "request_body": the RAW request object sent to OpenRouter. It MUST
     contain a non-empty "messages" array. If a call does not use
     chat-messages shape, convert it faithfully to
     {"messages": [{"role": ..., "content": ...}]} when logging.
   - "response_body": the RAW OpenRouter response object in its FINAL,
     non-streaming shape. Do not truncate or reshape it. For streamed
     calls, consume the stream to the very end — OpenRouter sends the
     usage object (cost + tokens) ONLY in the final chunk — then log the
     assembled complete response (full content + that final usage).
   - "cost_usd": from the response's usage object, compute
     usage.cost + (usage.cost_details.upstream_inference_cost or 0).
     This is correct in BOTH billing modes: on credit-billed accounts
     usage.cost is the full charge; on bring-your-own-key accounts
     usage.cost is only OpenRouter's fee and the real inference cost is
     in upstream_inference_cost. Do not compute costs from price tables —
     the exact number is in every response.
   - "generation_id": the response body's "id" field.
   - "prompt_tokens" and "completion_tokens": from the response's usage.
   - Optional: "latency_ms", "cache_read_tokens" (from
     usage.prompt_tokens_details.cached_tokens if present),
     "cache_creation_tokens", "cache_hit".
   OMIT any field you do not have a value for — never write null.
   Logging is append-only and must NEVER block, slow, or alter the real
   call. If logging fails, the app must behave exactly as before.

4. Only log (and count) a line if the response contains a real assistant
   message — non-empty text content or tool calls. Never log OpenRouter or
   provider ERROR responses as capture lines.

5. Stop at 100 logged lines per (tag, served model) PAIR — then keep
   counting. OpenRouter can serve one prompt with different models, so the
   cap is per pair: once a (tag, model) pair has 100 full lines, log no
   more full lines for that pair, but KEEP logging models still under 100
   that appear for the same tag, and count every further call.

6. Run for AT LEAST 24 hours of wall-clock time under normal production
   traffic, even if every pair reaches its 100 much sooner — the savings
   projection is computed from the capture window and needs a full daily
   cycle. Keep counting calls until the window ends.

7. When the capture window ends, append ONE summary line for EVERY tag —
   not only the busy ones — using the same tag as its logged lines and the
   full window:
   {"type":"volume_summary","tag":"<id>",
    "observed_requests":<total calls for this tag in the window, including
      the logged ones>,
    "window_start":"<iso start of the whole capture window>",
    "window_end":"<iso end of the whole capture window>",
    "per_model":[{"model":"<served slug>","count":N,"avg_prompt_tokens":...,
      "avg_completion_tokens":...,"avg_baseline_cost_usd":...,
      "cache_hit_rate":...}]}
   Omit any per_model number you do not have — never write null.

8. If any logged line ends up with cost_usd of 0 for a model that is NOT a
   ":free" variant, backfill it after the window ends via
   GET https://openrouter.ai/api/v1/generation?id=<generation_id>
   (authenticated with the same OpenRouter API key) and use its total_cost.

9. NEVER log secrets, API keys, or auth headers — only the fields above.

When the file is ready, upload ./parity-capture.jsonl in the Parity Layer
dashboard under "Offline import". Parity Layer proves parity from it and
shows your projected savings. Nothing from your side is sent anywhere except
this one file you upload.

Pricing

You pay per-request, per-token, billed at the cheaper model's rate once a prompt type is proven and routed, 30-60% less than your baseline. Up to 10 prompts are free, no credit card. See the full breakdown on the pricing page.

FAQ

What is the Parity Layer API base URL?

https://api.paritylayer.com. Point your existing OpenAI or Anthropic SDK at it and authenticate with your Parity key (sk-pl-...). For OpenAI-style clients, use https://api.paritylayer.com/v1.

Do I have to change my code to use Parity Layer?

No. It is a two-line change: set the base URL to https://api.paritylayer.com and use your Parity key. Your prompts, tools, streaming, parameters, and response shapes stay identical to calling the provider directly.

How much does it save and does quality drop?

Typical savings are 30-60% of API spend depending on prompt type, so you pay 40-70% of your current bill. Quality is preserved: Parity only routes to a cheaper model after statistically proving it matches (or beats) your baseline on your prompts, and falls back to your baseline instantly if quality ever drifts. Output matches or beats your baseline, never worse.

Can I prove the savings before changing any code?

Yes. Export a sample of past requests as a JSONL file and Parity proves the savings offline, before you change your integration or send live traffic. Up to 10 prompts are free, no credit card.

Start free

Get a Parity key and prove the savings on your own prompts. Up to 10 prompts free, no credit card.

Get your API key