Documentation

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.

01 Base URL
https://poolmill.com/v1
02 Authenticate
Send your key as a bearer token: Authorization: Bearer sk-…
03 Pick a model ID
Model IDs are scoped to your account. List yours with GET /v1/models — see Model IDs.
bash
# 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."}]
  }'

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:

bash — list your models
curl https://poolmill.com/v1/models \
  -H "Authorization: Bearer $POOLMILL_API_KEY"
json — trimmed response
{
  "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" }
  ]
}
Building a portable app? Read the model list once at startup and select the entry you want, rather than embedding an ID in your source. That way a change of model — or a change of provider — is configuration, not code.

Available models

ModelInputCached inputOutputCache saving
Qwen3.6 Plus Qwen · Qwen/Qwen3.6-Plus $0.50 $0.10 $3.00
Qwen3.7 Flash Qwen · Qwen/Qwen3.7-Flash $0.03 $0.006 $0.13
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
DeepSeek v4 Flash Vision Exp DeepSeek · openrouter/deepseek/deepseek-v4-flash-vision-exp $0.15 $0.003 $0.60 50×
USD per 1M tokens, in the order input / cached input / output. Charged against your prepaid credit.

Chat completions

Standard OpenAI request and response shapes, including temperature, top_p, max_tokens, stop, tools and response_format.

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

python — streaming
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:

json — usage
{
  "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.

Practical note. Cache only helps when the beginning of the prompt is stable. Put long, unchanging content — system instructions, tool schemas, reference documents — at the start, and the varying part last.

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:

python — anthropic messages
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.

StatusMeaning
200Success.
401Missing or invalid key. Check the Authorization header.
403The key is valid but not permitted to use that model — usually an ID that is not in your package. List yours at /v1/models.
429Credit exhausted, or a rate limit was hit. The message distinguishes the two.
5xxUpstream capacity was unavailable. Safe to retry with backoff.

Out-of-credit responses look like this:

json — 429
{
  "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 429 or 5xx — 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:

python — retry with backoff
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.