polygate logopolygate

Documentation

One function, 27 providers, clouds and gateways — and anything you host yourself.

Install

# Python
pip install polygate

# TypeScript
npm install polygate

Python needs requests. TypeScript needs nothing — it uses the built-in fetch. That is the whole dependency list, which is the point: a client that unifies providers should not weigh more than the providers.

Your first call

import polygate
 
response = polygate.chat(
provider="anthropic",
model="claude-sonnet-4-5",
messages=[{"role": "user", "content": "Say hi in one word."}],
)
 
print(response.content) # "Hi!"
print(response.provider) # "anthropic"
print(response.usage) # Usage(prompt_tokens=12, completion_tokens=3, ...)

The key comes from ANTHROPIC_API_KEY here. Every provider has a standard variable — see its page — or pass api_key directly.

The response

Every provider returns the same shape, whatever it actually sent back. raw is the escape hatch: anything polygate does not normalize is still there, untouched.

contentthe assistant's reply text
rolealways "assistant"
modelthe model that actually served the request
providerwhich provider served it
usageprompt, completion and total token counts
rawthe untouched original provider response

Switching providers

This is the whole premise. Nothing about the call changes except the two strings naming where it goes.

# The only thing that changes is the provider and the model.
for provider, model in [
("openai", "gpt-4o"),
("anthropic", "claude-sonnet-4-5"),
("groq", "llama-3.3-70b-versatile"),
("bedrock", "anthropic.claude-3-5-sonnet-20241022-v2:0"),
]:
response = polygate.chat(
provider=provider, model=model,
messages=[{"role": "user", "content": "Say hi."}],
)
print(provider, response.content)

Retries

Retries are opt-in. A library that silently retries turns one request into several and one bill into several, so the default is exactly one attempt.

from polygate import chat, Retry
 
response = chat(
provider="openai",
model="gpt-4o",
messages=[{"role": "user", "content": "Hello"}],
# Omitted entirely means one request, one response, errors raised straight
# through. Retry() opts into backoff on 429/5xx while still failing fast on
# deterministic errors like a 400.
retry=Retry(max_attempts=4),
)

Key pools

# One key, a list of keys, or a comma-separated environment variable —
# all three are the same thing to polygate.
response = polygate.chat(
provider="openai",
model="gpt-4o",
messages=[{"role": "user", "content": "Hello"}],
api_key=["sk-one", "sk-two", "sk-three"],
)
 
# Requests rotate round-robin. A key the provider rejects (401/403) is dropped
# for the rest of the process; a rate-limited one (429) is parked for its
# Retry-After and traffic moves on immediately — which recovers faster than
# waiting out a backoff.

Errors

from polygate.exceptions import (
MissingAPIKeyError, # no key passed and none in the environment
MissingEndpointError, # a cloud provider called without its endpoint
ProviderAPIError, # the provider returned a non-2xx
UnsupportedProviderError,
)
 
try:
polygate.chat(provider="openai", model="gpt-4o", messages=[...])
except ProviderAPIError as error:
print(error.provider, error.status_code, error.message)

Many requests at once

Two different tools, often confused: an offline batch job at roughly half price, and concurrent dispatch when you want the answers now. See Batching & concurrency.

Providers

Providers

Model vendors and inference hosts you call directly.

Clouds

Your own cloud account. Each needs an endpoint or region as well as a credential, because the URL contains your resource.

Gateways

One key, many vendors. Model ids here are usually written vendor/model.

Self-hosted

Anything you run that speaks the OpenAI format.