Documentation
The API is OpenAI-compatible. If your code already calls an OpenAI-style endpoint, you are changing two strings: the base URL and the model name.
Quickstart
Three steps: base URL, key, model. Your key comes from the console.
https://poolmill.com/v1
Send your key as a bearer token:
Authorization: Bearer sk-…
Model IDs are scoped to your account. List yours with
GET /v1/models — see
Model IDs.
# 1. put your key in the environment
export POOLMILL_API_KEY="sk-..."
# 2. call the OpenAI-compatible endpoint
curl https://poolmill.com/v1/chat/completions \
-H "Authorization: Bearer $POOLMILL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek/deepseek-v4.1-flash",
"messages": [{"role": "user", "content": "Say hello."}]
}'
from openai import OpenAI
client = OpenAI(
base_url="https://poolmill.com/v1",
api_key="sk-...", # from the console
)
r = client.chat.completions.create(
model="deepseek/deepseek-v4.1-flash",
messages=[{"role": "user", "content": "Say hello."}],
)
print(r.choices[0].message.content)
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://poolmill.com/v1",
apiKey: process.env.POOLMILL_API_KEY,
});
const r = await client.chat.completions.create({
model: "deepseek/deepseek-v4.1-flash",
messages: [{ role: "user", content: "Say hello." }],
});
console.log(r.choices[0].message.content);
Model IDs
Model IDs are plain vendor/model strings — for example
deepseek/deepseek-v4.1-flash. They are the same for every customer:
there is no account prefix and no per-account naming to keep in sync.
What a key can reach is scoped by the key itself, not by the name. Your key
lists the models in the package you bought; asking for any other model returns
403.
Discover what your key can reach at runtime:
curl https://poolmill.com/v1/models \
-H "Authorization: Bearer $POOLMILL_API_KEY"
{
"object": "list",
"data": [
{ "id": "deepseek/deepseek-v4.1-flash", "object": "model", "created": 1677610602, "owned_by": "openai" },
{ "id": "Qwen/Qwen3.6-Plus", "object": "model", "created": 1677610602, "owned_by": "openai" }
]
}
Available models
| Model | Input | Cached input | Output | Cache saving |
|---|---|---|---|---|
| Qwen3.6 Plus Qwen · Qwen/Qwen3.6-Plus | $0.50 | $0.10 | $3.00 | 5× |
| Qwen3.7 Flash Qwen · Qwen/Qwen3.7-Flash | $0.03 | $0.006 | $0.13 | 5× |
| Qwen3.8 Flash Qwen · Qwen/Qwen3.8-Flash | $0.16 | $0.016 | $0.47 | 10× |
| DeepSeek v4.1 Flash DeepSeek · deepseek/deepseek-v4.1-flash | $0.15 | $0.003 | $0.60 | 50× |
| Meta Muse Spark 1.2 Contributor Meta · meta/muse-spark-1.2-contributor | $0.10 | $0.002 | $0.20 | 50× |
| Meta Muse Spark 1.3 Contributor Meta · meta/muse-spark-1.3-contributor | $0.10 | $0.002 | $0.20 | 50× |
| Z.ai GLM 5.3 Flash Z.ai · z-ai/glm-5.3-flash | $0.15 | $0.03 | $0.50 | 5× |
| DeepSeek v4 Flash Vision Exp DeepSeek · openrouter/deepseek/deepseek-v4-flash-vision-exp | $0.15 | $0.003 | $0.60 | 50× |
Chat completions
Standard OpenAI request and response shapes, including temperature,
top_p, max_tokens, stop,
tools and response_format.
curl https://poolmill.com/v1/chat/completions \
-H "Authorization: Bearer $POOLMILL_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek/deepseek-v4.1-flash",
"messages": [
{"role": "system", "content": "You are terse."},
{"role": "user", "content": "What is a vector index?"}
],
"max_tokens": 200,
"temperature": 0.2
}'
The response includes a usage block; see
Cached input for how cache is reported.
Streaming
Pass "stream": true to receive server-sent events. Any
OpenAI-compatible client library handles this transparently — no special headers
are required.
from openai import OpenAI
client = OpenAI(base_url="https://poolmill.com/v1", api_key="sk-...")
stream = client.chat.completions.create(
model="deepseek/deepseek-v4.1-flash",
messages=[{"role": "user", "content": "Write a haiku about caching."}],
stream=True,
)
for chunk in stream:
delta = chunk.choices[0].delta.content
if delta:
print(delta, end="", flush=True)
Cached input
When a prompt repeats content the upstream provider has already seen, those tokens are billed at the cached rate — on DeepSeek v4.1 Flash that is 50× cheaper than standard input. It applies automatically; there is nothing to enable.
The cached portion is reported in the usage block:
{
"usage": {
"prompt_tokens": 1233,
"completion_tokens": 5,
"total_tokens": 1238,
"prompt_tokens_details": { "cached_tokens": 1024 }
}
}
Read prompt_tokens_details.cached_tokens to see how much of your prompt
was served from cache. Your console reports the same split per request.
Anthropic SDK
Anthropic-shaped requests are accepted at the same endpoint and are carried through to the model you name. Use the Anthropic SDK with a custom base URL:
from anthropic import Anthropic
client = Anthropic(base_url="https://poolmill.com/v1", api_key="sk-...")
msg = client.messages.create(
model="deepseek/deepseek-v4.1-flash",
max_tokens=256,
stream=True,
messages=[{"role": "user", "content": "Summarise this release note."}],
)
for event in msg:
print(event)
Errors
Errors are returned as JSON with an error object, matching the OpenAI
shape, and carry the HTTP status below.
| Status | Meaning |
|---|---|
| 200 | Success. |
| 401 | Missing or invalid key. Check the Authorization header. |
| 403 | The key is valid but not permitted to use that model — usually an ID that is not in your package. List yours at /v1/models. |
| 429 | Credit exhausted, or a rate limit was hit. The message distinguishes the two. |
| 5xx | Upstream capacity was unavailable. Safe to retry with backoff. |
Out-of-credit responses look like this:
{
"error": {
"message": "Budget has been exceeded! Current cost: 9.5, Max budget: 9.0",
"type": "budget_exceeded",
"code": "429"
}
}
Rate limits
Capacity is pooled upstream, which is what makes the pricing possible. In practice that means:
- Steady production traffic is unaffected.
- Extreme bursts at peak times may be rate-limited upstream and surface as
429or5xx— retry with exponential backoff. - Very large requests (long contexts, big tool schemas) take the same path; the endpoint allows request bodies up to 64 MB.
A minimal retry pattern:
import random, time
from openai import OpenAI, APIStatusError
client = OpenAI(base_url="https://poolmill.com/v1", api_key="sk-...")
def complete(**kw):
for attempt in range(5):
try:
return client.chat.completions.create(**kw)
except APIStatusError as e:
# 429 = no credit or rate limit; 5xx = upstream capacity
if e.status_code < 500 and e.status_code != 429:
raise
time.sleep(min(2 ** attempt + random.random(), 20))
raise RuntimeError("giving up after 5 attempts")
Ready to make the first call?
Buy credit, create a key, point your SDK at the base URL.