SDKs
Use the official OpenAI and Anthropic SDKs with this API, plus a small helper for media jobs.
There is no SDK package of our own. Use the official OpenAI or Anthropic SDK for text, and plain HTTP for media jobs (a copy-paste helper is below).
| SDK | Base URL | Install |
|---|---|---|
| OpenAI (Python, Node.js and others) | https://pinerouter.ai/v1 | pip install openai / npm install openai |
| Anthropic (Python, Node.js and others) | https://pinerouter.ai (no /v1) | pip install anthropic / npm install @anthropic-ai/sdk |
The key is a key from API keys. Keep it in an environment variable, not in code.
OpenAI SDK
import os
from openai import OpenAI
client = OpenAI(base_url="https://pinerouter.ai/v1", api_key=os.environ["API_KEY"])
r = client.chat.completions.create(
model="gpt-6-luna",
messages=[{"role": "user", "content": "Name three rivers in Europe."}],
)
print(r.choices[0].message.content)
img = client.images.generate(model="z-image", prompt="A red paper boat on a calm lake", size="1024x1024")
print(img.data[0].url)import OpenAI from "openai";
const client = new OpenAI({ baseURL: "https://pinerouter.ai/v1", apiKey: process.env.API_KEY });
const r = await client.chat.completions.create({
model: "gpt-6-luna",
messages: [{ role: "user", content: "Name three rivers in Europe." }],
});
console.log(r.choices[0].message.content);
const img = await client.images.generate({ model: "z-image", prompt: "A red paper boat on a calm lake", size: "1024x1024" });
console.log(img.data[0].url);client.responses.create(...) works too. The SDK retries 429 and 5xx on its own (2 retries by default; set max_retries / maxRetries). Raise its timeout for image calls, which wait for the image: OpenAI(..., timeout=300).
Anthropic SDK
import os
import anthropic
client = anthropic.Anthropic(base_url="https://pinerouter.ai", api_key=os.environ["API_KEY"])
msg = client.messages.create(
model="gpt-5-2",
max_tokens=500,
messages=[{"role": "user", "content": "Name three rivers in Europe."}],
)
print(msg.content[0].text)import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic({ baseURL: "https://pinerouter.ai", apiKey: process.env.API_KEY });
const msg = await client.messages.create({
model: "gpt-5-2",
max_tokens: 500,
messages: [{ role: "user", content: "Name three rivers in Europe." }],
});
console.log(msg.content[0].text);Any text model on Models answers in Messages format. Claude models are not on the list today.
Jobs helper
For media models with all their settings, use Jobs. This helper starts a job, polls it every 5 seconds, and downloads the files. Copy it into your project.
# pip install requests
import json, os, time, requests
BASE = "https://pinerouter.ai"
H = {"Authorization": f"Bearer {os.environ['API_KEY']}"}
def run_job(model, input, every=5, max_wait=1800):
r = requests.post(f"{BASE}/v1/jobs", headers=H, json={"model": model, "input": input}, timeout=60)
body = r.json()
if r.status_code != 200 or body.get("code") != 200:
raise RuntimeError(f"start failed ({r.status_code}): {body}")
task = body["data"]["id"]
waited = 0
while waited < max_wait:
time.sleep(every)
waited += every
r = requests.get(f"{BASE}/v1/jobs/{task}", headers=H, timeout=60)
if r.status_code == 429 or r.status_code >= 500:
continue # poll again; never start the job a second time
d = r.json()["data"]
if d["status"] == "succeeded":
return task, d["outputs"]
if d["status"] == "failed":
raise RuntimeError(f"job {task} failed: {d.get('error')}")
raise TimeoutError(f"job {task} still running; poll it later")
def download(urls, folder="."):
paths = []
for i, url in enumerate(urls):
name = os.path.join(folder, f"{i}_{url.split('/')[-1].split('?')[0]}")
with requests.get(url, stream=True, timeout=300) as r, open(name, "wb") as f:
r.raise_for_status()
for chunk in r.iter_content(1 << 20):
f.write(chunk)
paths.append(name)
return paths
task, urls = run_job("z-image", {"prompt": "A red paper boat on a calm lake", "aspect_ratio": "1:1"})
print(download(urls))// Node.js 18 or newer (built-in fetch)
import { writeFile } from "node:fs/promises";
const BASE = "https://pinerouter.ai";
const H = { Authorization: `Bearer ${process.env.API_KEY}`, "Content-Type": "application/json" };
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
export async function runJob(model, input, { every = 5000, maxWait = 1_800_000 } = {}) {
const r = await fetch(`${BASE}/v1/jobs`, { method: "POST", headers: H, body: JSON.stringify({ model, input }) });
const body = await r.json();
if (r.status !== 200 || body.code !== 200) throw new Error(`start failed (${r.status}): ${JSON.stringify(body)}`);
const task = body.data.id;
for (let waited = 0; waited < maxWait; waited += every) {
await sleep(every);
const p = await fetch(`${BASE}/v1/jobs/${task}`, { headers: H });
if (p.status === 429 || p.status >= 500) continue; // poll again; never start the job a second time
const d = (await p.json()).data;
if (d.status === "succeeded") return { task, urls: d.outputs };
if (d.status === "failed") throw new Error(`job ${task} failed: ${d.error}`);
}
throw new Error(`job ${task} still running; poll it later`);
}
export async function download(urls) {
const paths = [];
for (const [i, url] of urls.entries()) {
const name = `${i}_${new URL(url).pathname.split("/").pop()}`;
await writeFile(name, Buffer.from(await (await fetch(url)).arrayBuffer()));
paths.push(name);
}
return paths;
}
const { urls } = await runJob("z-image", { prompt: "A red paper boat on a calm lake", aspect_ratio: "1:1" });
console.log(await download(urls));- Result links last 7 days, so the helper downloads them straight away.
- A job that fails costs nothing. A job still running after the wait is not lost: keep the
idand poll it later. - Each model's fields are on its page under Models.
