oxyy.ai
HomeModelsProvidersPricingBlogDocs
Sign inGet API key
  1. Home/
  2. Docs
Getting started
IntroductionAuthenticationQuick start
Guides
ModelsStreamingTool callingStructured outputsMultimodal inputReasoningPrompt cachingUsage & cost
API reference
Chat completions88Responses APIImages13Video7Text to speech20Speech to text13Embeddings1File inputs
Vendor SDKs
Anthropic SDKGoogle GenAI SDK
Reference
SDKs & librariesRate limitsIdempotencyErrors
Documentation

Oxyy API Documentation

Oxyy gives you one endpoint and one API key for 200+ models — text, vision, images, video, speech and embeddings — behind the OpenAI wire format. If your code already speaks to OpenAI, changing the base URL is the whole migration.

Base URL https://api.oxyy.ai/v1 — set it as base_url (Python) or baseURL (JavaScript) and keep the rest of your code. The Anthropic and Google SDKs use the bare host instead; see Vendor SDKs.

What you can call

EndpointWhat it doesShape
POST /v1/chat/completionsText, vision, tools, structured output, and images answered over chatSync or streaming
POST /v1/responsesThe OpenAI Responses API, for the Agents SDK and CodexSync or streaming
POST /v1/images/generationsText to imageSync, or a job with async
POST /v1/images/edits
POST /v1/images/variations
Edit or vary an existing imageSync
POST /v1/videos/generations
POST /v1/videos/image-to-video
Text or image to videoJob — always asynchronous
POST /v1/audio/speechText to speech, and musicAudio bytes, or a job with async
POST /v1/audio/transcriptions
POST /v1/audio/translations
Speech to text, and speech to English textSync
POST /v1/embeddingsVectors for search, clustering and RAGSync
GET /v1/modelsThe catalogue your key can reach, with pricingSync
POST /v1/assets/uploadHost a file for 12 hours and get a URL to referenceSync
POST /v1/messagesAnthropic Messages API — Claude modelsSync or streaming
POST /v1beta/models/{model}:generateContentGoogle GenAI API — Gemini modelsSync or streaming
Authentication

Authentication

Every request carries your API key. Create one on the API keys page after signing up. The standard form is a bearer token:

# The standard form — every endpoint accepts it.
curl https://api.oxyy.ai/v1/models \
  -H "Authorization: Bearer $OXYY_API_KEY"

# Vendor-SDK forms, accepted on every endpoint so an SDK
# configured for Anthropic or Google needs no interception.
curl https://api.oxyy.ai/v1/models -H "x-api-key: $OXYY_API_KEY"
curl https://api.oxyy.ai/v1/models -H "x-goog-api-key: $OXYY_API_KEY"
# Read the key from the environment. Never commit it, and never
# ship it in browser or mobile code — anyone can read it there.
import os
from openai import OpenAI

client = OpenAI(
    api_key=os.environ["OXYY_API_KEY"],
    base_url="https://api.oxyy.ai/v1"
)

Three header forms are accepted on every endpoint, so an SDK written for another vendor works without interception:

FormSent byExample
Authorization: Bearer <key>OpenAI SDK, plain HTTPAuthorization: Bearer sk-oxyy-…
x-api-key: <key>Anthropic SDKx-api-key: sk-oxyy-…
x-goog-api-key: <key> or ?key=Google GenAI SDKx-goog-api-key: sk-oxyy-…
Keep the key server-side. Anyone who can read your browser or mobile bundle can read the key in it and spend your balance. Call Oxyy from your own backend and keep the key in an environment variable.
Quick start

Quick start

1
Get a key
Sign up and create one on the API keys page. Export it as OXYY_API_KEY.
2
Point your SDK at Oxyy
Install the official OpenAI SDK and set the base URL to https://api.oxyy.ai/v1. Nothing else changes.
3
Pick a model
Use any id from the model list below or from GET /v1/models.
# pip install openai
import os
from openai import OpenAI

client = OpenAI(
    api_key=os.environ["OXYY_API_KEY"],
    base_url="https://api.oxyy.ai/v1"
)

response = client.chat.completions.create(
    model="gpt-5",
    messages=[{"role": "user", "content": "Hello!"}]
)

print(response.choices[0].message.content)
print(response.usage.cost, "USD")
// npm install openai
import OpenAI from 'openai';

const client = new OpenAI({
  apiKey: process.env.OXYY_API_KEY,
  baseURL: 'https://api.oxyy.ai/v1'
});

const response = await client.chat.completions.create({
  model: 'gpt-5',
  messages: [{ role: 'user', content: 'Hello!' }]
});

console.log(response.choices[0].message.content);
console.log(response.usage.cost, 'USD');
curl https://api.oxyy.ai/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $OXYY_API_KEY" \
  -d '{"model": "gpt-5", "messages": [{"role": "user", "content": "Hello!"}]}'
Every response prices itself. usage.cost is what the request charged, in USD. You never have to open a dashboard to find out what a call cost — see Usage & cost.
Models

Models & discovery

The catalogue is live: models are added, repriced and retired without a release on your side. Read it from the API rather than hard-coding a list.

GEThttps://api.oxyy.ai/v1/models
GEThttps://api.oxyy.ai/v1/models/{model}one model, in detail

The list returns every enabled model with its pricing, context window and modalities. Two fields are Oxyy's own: accessible says whether your tier may call that model, and access_reason says why not when it is false — so a model picker can grey a row out and explain itself. The detail endpoint adds every parameter described rather than merely named, the endpoints and SDKs that serve it, and your own rate limits on it.

# Every model your key can reach, with pricing and capabilities.
curl https://api.oxyy.ai/v1/models \
  -H "Authorization: Bearer $OXYY_API_KEY"

# One model, with every parameter described rather than just named.
curl https://api.oxyy.ai/v1/models/gpt-5 \
  -H "Authorization: Bearer $OXYY_API_KEY"
import os
from openai import OpenAI

client = OpenAI(
    api_key=os.environ["OXYY_API_KEY"],
    base_url="https://api.oxyy.ai/v1"
)

# `accessible` says whether YOUR tier may call it; `access_reason`
# says why not when it is false.
for m in client.models.list().data:
    if m.oxyy["type"] == "TEXT" and m.accessible:
        print(m.id, m.oxyy["contextWindow"])

Example response

Response
{
  "object": "list",
  "total_count": 207,
  "data": [
    {
      "id": "gpt-5",
      "object": "model",
      "created": 1700000000,
      "owned_by": "openai",
      "accessible": true,
      "access_reason": null,
      "oxyy": {
        "displayName": "GPT-5",
        "type": "TEXT",
        "contextWindow": 400000,
        "maxTokens": 128000,
        "inputModalities": ["text", "image"],
        "outputModalities": ["text"],
        "supportedParameters": ["tools", "response_format", "reasoning_effort"],
        "pricing": { "input": 1.25, "output": 10, "cacheRead": 0.125, "unit": "per_1m_tokens" },
        "accessible": true
      }
    }
  ]
}

Which endpoint serves which model

Each model has a type, and each endpoint serves a set of types. Send a model to the wrong endpoint and the answer is a 400 with code model_type_mismatch that names the endpoint you wanted — before anything is billed.

EndpointModel typesNotes
POST /v1/chat/completionsAny chat-served modelText, vision, and image models answered over chat
POST /v1/responsesTEXT, IMAGE_TO_TEXT, IMAGE_GENERATIONThe Responses object model has no item for audio, video or vectors
POST /v1/images/*IMAGE_GENERATION, IMAGE_TO_IMAGE
POST /v1/videos/*TEXT_TO_VIDEO, IMAGE_TO_VIDEO, VIDEO_TO_VIDEO
POST /v1/audio/speechAUDIO_TTS, MUSIC_GENERATIONMusic is billed and returned as speech-shaped audio
POST /v1/audio/transcriptions
POST /v1/audio/translations
AUDIO_STT
POST /v1/embeddingsEMBEDDING
Streaming

Streaming

Set stream: true on chat completions, responses or either vendor SDK and the reply arrives as server-sent events. Every official SDK handles the framing for you; the shape below is what you get over plain HTTP.

import os
from openai import OpenAI

client = OpenAI(
    api_key=os.environ["OXYY_API_KEY"],
    base_url="https://api.oxyy.ai/v1"
)

stream = client.chat.completions.create(
    model="gpt-5",
    messages=[{"role": "user", "content": "Write a haiku about caching."}],
    stream=True,
    stream_options={"include_usage": True},  # final chunk carries usage + cost
)

for chunk in stream:
    if chunk.choices and chunk.choices[0].delta.content:
        print(chunk.choices[0].delta.content, end="", flush=True)
    elif chunk.usage:
        print(f"\n\ncost: {chunk.usage.cost}")
import OpenAI from 'openai';

const client = new OpenAI({
  apiKey: process.env.OXYY_API_KEY,
  baseURL: 'https://api.oxyy.ai/v1'
});

const stream = await client.chat.completions.create({
  model: 'gpt-5',
  messages: [{ role: 'user', content: 'Write a haiku about caching.' }],
  stream: true,
  stream_options: { include_usage: true },
});

for await (const chunk of stream) {
  const delta = chunk.choices?.[0]?.delta?.content;
  if (delta) process.stdout.write(delta);
  if (chunk.usage) console.log('\ncost:', chunk.usage.cost);
}
curl -N https://api.oxyy.ai/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $OXYY_API_KEY" \
  -d '{
    "model": "gpt-5",
    "messages": [{"role": "user", "content": "Hello!"}],
    "stream": true,
    "stream_options": {"include_usage": true}
  }'

On the wire

text/event-stream
data: {"id":"chatcmpl-abc123","object":"chat.completion.chunk","created":1700000000,"model":"gpt-5","choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null}]}

data: {"id":"chatcmpl-abc123","object":"chat.completion.chunk","created":1700000000,"model":"gpt-5","choices":[{"index":0,"delta":{"content":"Cold"},"finish_reason":null}]}

data: {"id":"chatcmpl-abc123","object":"chat.completion.chunk","created":1700000000,"model":"gpt-5","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}

// Only when stream_options.include_usage is true — choices is empty here.
data: {"id":"chatcmpl-abc123","object":"chat.completion.chunk","created":1700000000,"model":"gpt-5","choices":[],"usage":{"prompt_tokens":12,"completion_tokens":17,"total_tokens":29,"cost":0.000106}}

data: [DONE]
ParameterTypeRequiredDescription
streambooleanOptionalStream the reply as server-sent events. Default false
stream_options.include_usagebooleanOptionalEmit one final chunk carrying usage — token counts and cost — with an empty choices array, just before [DONE]. Default false
Things worth knowing. The stream always ends with data: [DONE]. Closing the connection cancels the upstream generation, and you are billed only for what was produced. A failure that happens after the first byte arrives as an error frame inside the stream rather than an HTTP status, because the status was already sent.Idempotency-Key is ignored on streaming requests — a stream cannot be replayed.
Tool calling

Tool & function calling

Describe your functions, and a model that supports tools will ask you to run one. Oxyy translates the declaration into whatever each provider expects, so the same tools array works across OpenAI, Anthropic, Google, xAI and the rest.

ParameterTypeRequiredDescription
toolsarrayOptionalFunction declarations: {type: "function", function: {name, description, parameters}}, where parameters is a JSON Schema object.
tool_choicestring|objectOptionalHow freely the model may call a tool. One of:noneautorequired{type:"function",function:{name}} Default auto
parallel_tool_callsbooleanOptionalAllow more than one tool call per turn. Default true
import json, os
from openai import OpenAI

client = OpenAI(
    api_key=os.environ["OXYY_API_KEY"],
    base_url="https://api.oxyy.ai/v1"
)

tools = [{
    "type": "function",
    "function": {
        "name": "get_weather",
        "description": "Current weather for a city.",
        "parameters": {
            "type": "object",
            "properties": {"city": {"type": "string"}},
            "required": ["city"],
        },
    },
}]

messages = [{"role": "user", "content": "Weather in Dhaka?"}]
response = client.chat.completions.create(
    model="gpt-5", messages=messages, tools=tools, tool_choice="auto"
)

call = response.choices[0].message.tool_calls[0]
args = json.loads(call.function.arguments)

# Run the tool yourself, then send the result back as a tool message.
messages.append(response.choices[0].message)
messages.append({
    "role": "tool",
    "tool_call_id": call.id,
    "content": json.dumps({"temp_c": 31, "sky": "humid"}),
})

final = client.chat.completions.create(model="gpt-5", messages=messages, tools=tools)
print(final.choices[0].message.content)
import OpenAI from 'openai';

const client = new OpenAI({
  apiKey: process.env.OXYY_API_KEY,
  baseURL: 'https://api.oxyy.ai/v1'
});

const tools = [{
  type: 'function',
  function: {
    name: 'get_weather',
    description: 'Current weather for a city.',
    parameters: {
      type: 'object',
      properties: { city: { type: 'string' } },
      required: ['city'],
    },
  },
}];

const messages = [{ role: 'user', content: 'Weather in Dhaka?' }];
const res = await client.chat.completions.create({
  model: 'gpt-5', messages, tools, tool_choice: 'auto',
});

const call = res.choices[0].message.tool_calls[0];
const args = JSON.parse(call.function.arguments);

messages.push(res.choices[0].message, {
  role: 'tool',
  tool_call_id: call.id,
  content: JSON.stringify({ temp_c: 31, sky: 'humid' }),
});

const final = await client.chat.completions.create({ model: 'gpt-5', messages, tools });
console.log(final.choices[0].message.content);

The tool-call turn

Response
{
  "id": "chatcmpl-abc123",
  "object": "chat.completion",
  "created": 1700000000,
  "model": "gpt-5",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": null,
        "tool_calls": [
          {
            "id": "call_abc123",
            "type": "function",
            "function": {
              "name": "get_weather",
              "arguments": "{\"city\":\"Dhaka\"}"
            }
          }
        ],
        "refusal": null
      },
      "logprobs": null,
      "finish_reason": "tool_calls"
    }
  ],
  "usage": { "prompt_tokens": 61, "completion_tokens": 18, "total_tokens": 79, "cost": 0.00029 }
}
The loop. Append the assistant message as it came back, then one {role:"tool", tool_call_id, content}message per call, and send the whole conversation again. finish_reason is tool_callswhenever calls are present, even on providers that report stop. On reasoning models, send back any reasoning_details unmodified so the model's own thinking survives the round trip.
Function tools only. Oxyy runs no hosted tools of its own — there is no web search, code interpreter or file search to enable. Declare a function and run it in your application. Anthropic's server tools are the exception: they are forwarded to the provider untouched over the Anthropic SDK.
Structured outputs

Structured outputs

response_format makes the model answer with JSON instead of prose. Use json_schemawhen you have a shape to enforce, and json_object when you only need valid JSON.

ParameterTypeRequiredDescription
response_formatobjectOptionalThe output contract. Forms:{type:"text"}{type:"json_object"}{type:"json_schema", json_schema:{name, schema, strict}}
import json, os
from openai import OpenAI

client = OpenAI(
    api_key=os.environ["OXYY_API_KEY"],
    base_url="https://api.oxyy.ai/v1"
)

response = client.chat.completions.create(
    model="gpt-5",
    messages=[{"role": "user", "content": "Extract the invoice fields."}],
    response_format={
        "type": "json_schema",
        "json_schema": {
            "name": "invoice",
            "strict": True,
            "schema": {
                "type": "object",
                "properties": {
                    "total": {"type": "number"},
                    "currency": {"type": "string"},
                },
                "required": ["total", "currency"],
                "additionalProperties": False,
            },
        },
    },
)

data = json.loads(response.choices[0].message.content)
# json_object is the looser mode: valid JSON, no schema enforced.
curl https://api.oxyy.ai/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $OXYY_API_KEY" \
  -d '{
    "model": "gpt-5",
    "messages": [{"role": "user", "content": "List 3 colours as JSON."}],
    "response_format": {"type": "json_object"}
  }'
Portable by construction. Providers that have no native schema mode are given a forced output tool instead, and the answer is unwrapped for you — including stripping the ```json fence some models add. Either way choices[0].message.content is a JSON document you can parse. strict: true needs a fully closed schema (additionalProperties: false and every property in required); leave it off if yours is not.
Multimodal input

Images, audio & documents as input

Send an array of content parts instead of a string, and a model that accepts that modality will read them. Both the OpenAI spelling and the Responses spelling are understood on /v1/chat/completions:

PartCarriesSource
image_url / input_imageAn image{"url": "https://…"} or a data:image/…;base64,… URL
input_audioAn audio clip{"data": "<base64>", "format": "wav"}
file / input_fileA PDF or other document{"filename": "…", "file_data": "data:application/pdf;base64,…"}
import base64, os
from openai import OpenAI

client = OpenAI(
    api_key=os.environ["OXYY_API_KEY"],
    base_url="https://api.oxyy.ai/v1"
)

# A public https URL, or a data URL you build yourself.
with open("chart.png", "rb") as f:
    b64 = base64.b64encode(f.read()).decode()

response = client.chat.completions.create(
    model="gpt-5",
    messages=[{
        "role": "user",
        "content": [
            {"type": "text", "text": "What does this chart show?"},
            {"type": "image_url", "image_url": {
                "url": f"data:image/png;base64,{b64}",
                "detail": "auto",
            }},
        ],
    }],
)
print(response.choices[0].message.content)
curl https://api.oxyy.ai/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $OXYY_API_KEY" \
  -d '{
    "model": "gemini-3-pro",
    "messages": [{
      "role": "user",
      "content": [
        {"type": "text", "text": "Describe this image."},
        {"type": "image_url", "image_url": {"url": "https://example.com/photo.jpg"}}
      ]
    }]
  }'
# A PDF or other document rides in a `file` content part.
import base64, os
from openai import OpenAI

client = OpenAI(
    api_key=os.environ["OXYY_API_KEY"],
    base_url="https://api.oxyy.ai/v1"
)

with open("contract.pdf", "rb") as f:
    b64 = base64.b64encode(f.read()).decode()

response = client.chat.completions.create(
    model="claude-sonnet-4.5",
    messages=[{
        "role": "user",
        "content": [
            {"type": "text", "text": "Summarise the termination clause."},
            {"type": "file", "file": {
                "filename": "contract.pdf",
                "file_data": f"data:application/pdf;base64,{b64}",
            }},
        ],
    }],
)
A model only reads what it declares. Each model lists its inputModalities in GET /v1/models. Attaching an image to a text-only model is refused rather than quietly dropped, because a silently ignored attachment produces a confident answer about nothing. Per-file caps are 20 MB for images, 50 MB for audio and 100 MB for video and documents, and an operator can set a lower cap per model.
URLs are fetched by us, not by the provider. A URL you supply is downloaded, size-checked and inlined before the provider call, so private and loopback addresses are refused. If a URL is unreachable you get a 400 that says so, not a confusing provider error.
Reasoning

Reasoning

Reasoning models think before answering. reasoning_effort is the portable control — it is translated into each vendor's own dial, including Anthropic's thinking-token budget — and each vendor's native spelling is also accepted as sent.

ParameterTypeRequiredDescription
reasoning_effortstringOptionalHow much the model should think. One of:minimallowmediumhigh On Claude this becomes a thinking-token budget.
thinkingobjectOptionalAnthropic's native control, forwarded verbatim: {type: "enabled"|"adaptive"|"disabled", budget_tokens}.
import os
from openai import OpenAI

client = OpenAI(
    api_key=os.environ["OXYY_API_KEY"],
    base_url="https://api.oxyy.ai/v1"
)

response = client.chat.completions.create(
    model="claude-opus-4.6",
    messages=[{"role": "user", "content": "Prove that sqrt(2) is irrational."}],
    reasoning_effort="high",  # minimal | low | medium | high
)

msg = response.choices[0].message
print(msg.reasoning_content)   # the model's thinking, when it emits any
print(msg.content)
print(response.usage.completion_tokens_details.reasoning_tokens)
# Anthropic-style thinking controls pass through untouched.
curl https://api.oxyy.ai/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $OXYY_API_KEY" \
  -d '{
    "model": "claude-opus-4.6",
    "messages": [{"role": "user", "content": "Plan a migration."}],
    "max_tokens": 8000,
    "thinking": {"type": "enabled", "budget_tokens": 4000}
  }'
Field on the responseWhat it is
message.reasoning_contentThe thinking, as text, when the model emits any
message.reasoning_detailsStructured reasoning blocks. Send them back unmodified in a tool loop so signatures and encrypted reasoning survive.
usage.completion_tokens_details.reasoning_tokensThinking tokens, counted inside completion_tokens and billed at the output rate
Caching

Prompt caching

A repeated prefix — a long system prompt, a document, a tool catalogue — can be read from the provider's cache instead of being processed again, at a fraction of the input rate. Caching is reported on every response so you can verify it is working.

Field on <code>usage.prompt_tokens_details</code>What it is
cached_tokensInput tokens served from cache, billed at the cache-read rate
cache_write_tokensInput tokens written into the cache on this request
cache_creation.ephemeral_5m_input_tokens
cache_creation.ephemeral_1h_input_tokens
The TTL split of that write. The two are billed at different rates, so the split is reported rather than summed.
How to get a hit. Keep the stable part of your prompt first and byte-identical between calls, and put anything that varies at the end. On Claude, mark the breakpoint explicitly with cache_control on a system block — the Anthropic SDK passes it through per block. Whether a prefix is cached at all is the provider's decision, which is why the numbers above are reported rather than promised.
Usage

Usage & cost accounting

Every response carries what it cost. usage.cost is the exact amount charged in USD — the same number written to your invoice — so you can attribute spend per request without reconciling anything.

FieldWhat it is
usage.costWhat this request charged, in USD
usage.cost_detailsThe same amount split by axis (input, output, cache, image…), plus currency and any discount that applied
usage.is_byokAlways false: Oxyy serves every request on its own provider keys
X-Request-Id (header)The id this request is recorded under. Present on media endpoints and on every error; quote it in support requests.

On a stream, set stream_options.include_usage to receive the same object in the final chunk. Token counts are reported in both the chat vocabulary (prompt_tokens / completion_tokens) and the newer one (input_tokens / output_tokens), so one client library reading either shape sees the same numbers.

Which provider served you is not disclosed. Oxyy routes across multiple upstreams for availability and price, and does not return the provider that served a given request. The price you are charged is the price you were quoted for the model, whichever upstream answered.
Chat API

Chat completions

The main endpoint. GPT-5, Claude Opus 4.6, Gemini 3 Pro, DeepSeek, Grok, Llama and the rest — all with the same request body.

POSThttps://api.oxyy.ai/v1/chat/completions

Parameters

ParameterTypeRequiredDescription
modelstringRequiredA model id from GET /v1/models, e.g. gpt-5, claude-opus-4.6, gemini-3-pro.
messagesarrayRequiredThe conversation. Each item has a role and content; content may be a string or an array of content parts. Roles:systemdeveloperuserassistanttoolfunction
temperaturenumberOptionalSampling temperature, 0–2. Higher is more random. Default the provider's own
max_tokensintegerOptionalMaximum tokens to generate, ≥ 1. Default the provider's own, or the model's default output limit where the provider requires a value
top_pnumberOptionalNucleus sampling, 0–1. Use this or temperature, not both.
top_kintegerOptionalSample from the k most likely tokens. Forwarded to the providers that support it.
nintegerOptionalHow many completions to generate, 1–128. Every choice is billed. Default 1
streambooleanOptionalStream the reply as server-sent events. See Streaming. Default false
stream_optionsobjectOptional{include_usage: true} adds a final chunk with token counts and cost.
stopstring|arrayOptionalUp to four sequences that end the generation.
seedintegerOptionalBest-effort determinism on the providers that support it.
frequency_penaltynumberOptional-2–2. Discourages repeating tokens by frequency. Default 0
presence_penaltynumberOptional-2–2. Discourages repeating any token already used. Default 0
toolsarrayOptionalFunction declarations. See Tool calling.
tool_choicestring|objectOptionalOne of:noneautorequired{type:"function",…} Default auto
parallel_tool_callsbooleanOptionalAllow several tool calls in one turn. Default true
response_formatobjectOptionalForce JSON output. See Structured outputs. Types:textjson_objectjson_schema
reasoning_effortstringOptionalThinking budget on reasoning models. One of:minimallowmediumhigh
logprobsbooleanOptionalReturn log probabilities for the chosen tokens.
top_logprobsintegerOptionalHow many alternatives to report per position. Requires logprobs.
logit_biasobjectOptionalToken id to bias, -100–100.
userstringOptionalA stable id for your own end user. Helps you attribute usage.
modalitiesarrayOptionalWhat the model should return. Use ["image","text"] to have an image model answer over chat. One of:textimageaudio
image_configobjectOptionalPicture controls when a model answers with an image: {image_size, aspect_ratio}.
service_tierstringOptionalForwarded to providers that offer tiers, and echoed back on the response.
metadataobjectOptionalOpaque key/value pairs carried with the request.

Code examples

import os
from openai import OpenAI

client = OpenAI(
    api_key=os.environ["OXYY_API_KEY"],
    base_url="https://api.oxyy.ai/v1"
)

response = client.chat.completions.create(
    model="claude-sonnet-4.5",
    messages=[
        {"role": "system", "content": "You are a concise assistant."},
        {"role": "user", "content": "Explain HTTP caching in two sentences."},
    ],
    temperature=0.7,
    max_tokens=4096
)

print(response.choices[0].message.content)
import OpenAI from 'openai';

const client = new OpenAI({
  apiKey: process.env.OXYY_API_KEY,
  baseURL: 'https://api.oxyy.ai/v1'
});

const response = await client.chat.completions.create({
  model: 'claude-sonnet-4.5',
  messages: [
    { role: 'system', content: 'You are a concise assistant.' },
    { role: 'user', content: 'Explain HTTP caching in two sentences.' },
  ],
  temperature: 0.7,
  max_tokens: 4096
});

console.log(response.choices[0].message.content);
curl https://api.oxyy.ai/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $OXYY_API_KEY" \
  -d '{
    "model": "claude-sonnet-4.5",
    "messages": [{"role": "user", "content": "Explain HTTP caching."}],
    "temperature": 0.7,
    "max_tokens": 4096
  }'
// composer require openai-php/client
$client = OpenAI::factory()
    ->withApiKey(getenv('OXYY_API_KEY'))
    ->withBaseUri('https://api.oxyy.ai/v1')
    ->make();

$response = $client->chat()->create([
    'model' => 'claude-sonnet-4.5',
    'messages' => [['role' => 'user', 'content' => 'Hello!']],
    'temperature' => 0.7,
]);

echo $response->choices[0]->message->content;
// go get github.com/sashabaranov/go-openai
cfg := openai.DefaultConfig(os.Getenv("OXYY_API_KEY"))
cfg.BaseURL = "https://api.oxyy.ai/v1"
client := openai.NewClientWithConfig(cfg)

resp, err := client.CreateChatCompletion(context.Background(), openai.ChatCompletionRequest{
    Model: "claude-sonnet-4.5",
    Messages: []openai.ChatCompletionMessage{
        {Role: openai.ChatMessageRoleUser, Content: "Hello!"},
    },
})

Example response

Response
{
  "id": "chatcmpl-abc123def456",
  "object": "chat.completion",
  "created": 1700000000,
  "model": "claude-sonnet-4.5",
  "choices": [
    {
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "Hello! How can I help you today?",
        "refusal": null
      },
      "logprobs": null,
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "prompt_tokens": 12,
    "completion_tokens": 9,
    "total_tokens": 21,
    "prompt_tokens_details": {
      "cached_tokens": 0,
      "cache_write_tokens": 0,
      "text_tokens": 12,
      "image_tokens": null,
      "audio_tokens": null
    },
    "completion_tokens_details": {
      "reasoning_tokens": 0,
      "text_tokens": 9,
      "audio_tokens": null
    },
    "is_byok": false,
    "cost": 0.000106,
    "cost_details": { "currency": "USD", "input": 0.000036, "output": 0.00007 }
  }
}

finish_reason is one of stop, length, tool_calls, content_filter or function_call. When the provider reports something of its own, it is passed through beside it as native_finish_reason. An image answered over chat arrives on message.images.

Available models

88 models
Model
Model ID
chat-latest
chat-latest
Claude Fable 5
claude-fable-5
Claude Fable 5.1
claude-fable-5-1
Claude Haiku 4.5
claude-haiku-4-5-20251001
Claude Opus 4.5
claude-opus-4-5-20251101
Claude Opus 4.6
claude-opus-4-6
Claude Opus 4.7
claude-opus-4-7
Claude Opus 4.8
claude-opus-4-8
Claude Opus 5
claude-opus-5
Claude Sonnet 4.5
claude-sonnet-4-5-20250929
Claude Sonnet 4.6
claude-sonnet-4-6
Claude Sonnet 5
claude-sonnet-5
DeepSeek-V4-Pro-0813
deepseek-v4-pro
DeepSeek-V4.1-Flash
deepseek-flash
Gemini 2.5 Flash
gemini-2.5-flash
Gemini 2.5 Flash-Lite
gemini-2.5-flash-lite
Gemini 2.5 Pro
gemini-2.5-pro
Gemini 3 Flash
gemini-3-flash
Gemini 3.1 Flash-Lite
gemini-3.1-flash-lite
Gemini 3.1 Pro
gemini-3.1-pro-preview
Gemini 3.5 Flash
gemini-3.5-flash
Gemini 3.5 Flash-Lite
gemini-3.5-flash-lite
Gemini 3.6 Flash
gemini-3.6-flash
Gemini 3.7 Flash
gemini-3.7-flash
Gemini 3.8 Flash
gemini-3.8-flash
Gemma 4 12B Unified
gemma-4-12b-it
Gemma 4 26B A4B Instruct (MoE)
gemma-4-26b-a4b-it
Gemma 4 31B Instruct
gemma-4-31b-it
Gemma 4 E2B
gemma-4-e2b-it
Gemma 4 E4B
gemma-4-e4b-it
General-Purpose Translation
glm-translation-agent
GLM Slide/Poster Agent (beta)
glm-slide-poster-agent
GLM-4-32B-0414-128K
glm-4-32b-0414-128k
GLM-4.5
glm-4.5
GLM-4.5-Air
glm-4.5-air
GLM-4.5-AirX
glm-4.5-airx
GLM-4.5-Flash
glm-4.5-flash
GLM-4.5-X
glm-4.5-x
GLM-4.5V
glm-4.5v
GLM-4.6
glm-4.6
GLM-4.6V
glm-4.6v
GLM-4.6V-Flash
glm-4.6v-flash
GLM-4.6V-FlashX
glm-4.6v-flashx
GLM-4.7
glm-4.7
GLM-4.7-Flash
glm-4.7-flash
GLM-4.7-FlashX
glm-4.7-flashx
GLM-5
glm-5
GLM-5-Code
glm-5-code
GLM-5-Turbo
glm-5-turbo
GLM-5.1
glm-5.1
GLM-5.2
glm-5.2
GLM-5.3
glm-5.3
GLM-5.3-Flash
glm-5.3-flash
GLM-5.3-FlashX
glm-5.3-flashx
GLM-OCR
glm-ocr
GPT-4o
gpt-4o
GPT-4o mini
gpt-4o-mini
GPT-5.1
gpt-5.1
GPT-5.2
gpt-5.2
GPT-5.3 Codex
gpt-5.3-codex
GPT-5.4 mini
gpt-5.4-mini
GPT-5.5
gpt-5.5
GPT-5.6 Cyber (Daybreak Red)
gpt-5.6-cyber
GPT-5.6 Luna
gpt-5.6-luna
GPT-5.6 Sol
gpt-5.6-sol
GPT-5.6 Terra
gpt-5.6-terra
GPT-6 Astra
gpt-6-astra
GPT-Live 1
gpt-live-1
GPT-Realtime-1.5
gpt-realtime-1.5
GPT-Realtime-2
gpt-realtime-2
GPT-Realtime-2.1
gpt-realtime-2.1
GPT-Realtime-2.1 Mini
gpt-realtime-2.1-mini
Grok 4.20 (0309) Multi-Agent
grok-4.20-multi-agent-0309
Grok 4.20 (0309) Non-Reasoning
grok-4.20-0309-non-reasoning
Grok 4.20 (0309) Reasoning
grok-4.20-0309-reasoning
Grok 4.3
grok-4.3
Grok 4.5
grok-4.5
Grok 4.6
grok-4.6
Grok Build 0.1
grok-build-0.1
Grok Voice Think Fast 2.0
grok-voice-think-fast-2.0
Muse Glimmer
muse-glimmer
Muse Spark 1.1
muse-spark-1.1
Muse Spark 1.2
muse-spark-1.2
Muse Spark 1.2 (Contributor tier)
muse-spark-1.2-contributor
Muse Spark 1.3
muse-spark-1.3
Muse Spark 1.3 (Contributor tier)
muse-spark-1.3-contributor
Qwen3 30B A3B (FP8)
qwen3-30b-a3b-fp8
SAM 3.1
sam-3.1
Responses API

Responses API

The OpenAI Responses API, for clients built on client.responses.create such as the OpenAI Agents SDK. It runs on the same models, routing and billing as chat completions, and every chat model is available through it. Use the OpenAI SDK with base_url set to https://api.oxyy.ai/v1.

POSThttps://api.oxyy.ai/v1/responses
Stateless. Responses are not stored. Send the full conversation in input on every turn. previous_response_id is not supported, and neither are hosted tools such as web_search; both are refused with a 400 that says what to send instead.

Code examples

# pip install openai
import os
from openai import OpenAI

client = OpenAI(
    api_key=os.environ["OXYY_API_KEY"],
    base_url="https://api.oxyy.ai/v1"
)

response = client.responses.create(
    model="gpt-5",
    instructions="Answer in one sentence.",
    input="What is the capital of France?"
)

print(response.output_text)
// npm install openai
import OpenAI from 'openai';

const client = new OpenAI({
  apiKey: process.env.OXYY_API_KEY,
  baseURL: 'https://api.oxyy.ai/v1'
});

const response = await client.responses.create({
  model: 'gpt-5',
  instructions: 'Answer in one sentence.',
  input: 'What is the capital of France?'
});

console.log(response.output_text);
curl https://api.oxyy.ai/v1/responses \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $OXYY_API_KEY" \
  -d '{"model": "gpt-5", "instructions": "Answer in one sentence.", "input": "What is the capital of France?"}'

Example response

Response
{
  "id": "resp_abc123def456",
  "object": "response",
  "created_at": 1700000000,
  "status": "completed",
  "model": "gpt-5",
  "output": [
    {
      "id": "msg_abc123def456",
      "type": "message",
      "status": "completed",
      "role": "assistant",
      "content": [
        { "type": "output_text", "text": "The capital of France is Paris.", "annotations": [] }
      ]
    }
  ],
  "output_text": "The capital of France is Paris.",
  "previous_response_id": null,
  "store": false,
  "usage": {
    "input_tokens": 21,
    "input_tokens_details": { "cached_tokens": 0, "cache_write_tokens": 0 },
    "output_tokens": 8,
    "output_tokens_details": { "reasoning_tokens": 0 },
    "total_tokens": 29,
    "cost": 0.000106
  }
}

Multi-turn conversations

Keep the conversation in your application: append the previous output items and the next user message to input, then send all of it again. Reasoning and function-call items from output can be sent back as they are.

history = [{"role": "user", "content": "Name a prime number."}]

response = client.responses.create(model="gpt-5", input=history)
print(response.output_text)

# Nothing is stored between calls: append the reply and the next
# question, then send the whole conversation again.
history += [item.model_dump(exclude_none=True) for item in response.output]
history.append({"role": "user", "content": "Now double it."})

response = client.responses.create(model="gpt-5", input=history)
print(response.output_text)
const input = [{ role: 'user', content: 'Name a prime number.' }];

let response = await client.responses.create({ model: 'gpt-5', input });
console.log(response.output_text);

// Nothing is stored between calls: append the reply and the next
// question, then send the whole conversation again.
input.push(...response.output, { role: 'user', content: 'Now double it.' });
response = await client.responses.create({ model: 'gpt-5', input });
console.log(response.output_text);

Supported features

FeatureStatusNotes
input as a string or a list of itemsSupportedmessage, function_call, function_call_output and reasoning items
instructions, max_output_tokens, temperature, top_pSupported
streamSupportedServer-sent events such as response.output_text.delta and response.completed
Function tools, tool_choice, parallel_tool_callsSupportedtool_choice accepts none, auto, required, a function or allowed_tools
text.formatSupportedtext, json_object and json_schema
reasoning.effortSupported
input_image and input_file partsSupportedImages as a URL or a data URL; files as file_data (a data URL). file_id and file_url are refused.
Hosted tool: image_generationSupportedThe only hosted tool served here. Its size, quality, background and output_format drive an image model; other fields on the tool are reported as dropped.
store, include, truncation, metadataSupportedAccepted, but nothing is stored: store is always returned as false
previous_response_id, conversationNot supportedRefused with a 400. Send the full conversation in input instead.
background, stored prompt templates, item_reference itemsNot supportedRefused with a 400
Other hosted tools: web_search, file_search, code_interpreter, computer_use, mcpNot supportedRefused with a 400. Declare a function tool and run it in your application.
Retrieving, deleting or cancelling a response by IDNot supportedOnly POST /v1/responses is available
Image API

Image generation

DALL·E 3, Flux, Imagen 4, Nano Banana Pro, GPT Image and more — one request shape for all of them.

POSThttps://api.oxyy.ai/v1/images/generations

Parameters

ParameterTypeRequiredDescription
modelstringRequiredAn image model id, e.g. dall-e-3, flux.1-pro, nano-banana-pro.
promptstringRequiredWhat to draw. Up to 32,000 characters.
nintegerOptionalHow many images, 1–10. Each one is billed. Default 1
sizestringOptionalA pixel size such as 1024x1024, a named tier, or auto. A pixel size on a tier-priced model is mapped up to the smallest tier that contains it. Accepted:0.5K1K2K4KautoWIDTHxHEIGHT
aspect_ratiostringOptionalFor models that take a ratio rather than a size, e.g. 16:9.
qualitystringOptionalOne of:autohighmediumlowhdstandard
stylestringOptionalDALL·E 3. One of:vividnatural
backgroundstringOptionalGPT Image models. One of:transparentopaqueauto
output_formatstringOptionalOne of:pngjpegwebp
output_compressionintegerOptional0–100, for jpeg and webp.
response_formatstringOptionalHow the image comes back. One of:urlb64_json
negative_promptstringOptionalWhat to avoid. Forwarded to the models that accept it.
seedintegerOptionalReproducibility, where the provider supports it.
asyncbooleanOptionalReturn a job immediately (202) instead of waiting. See Jobs. Default false
userstringOptionalA stable id for your own end user.
A model only offers the sizes it declares. Ask for a named tier a model does not have and the request is refused before any provider is called, with the list it does support. Pixel sizes and auto always pass, because that is what the OpenAI SDK sends by default. The exact options per model are in GET /v1/models/{model}.

Code examples

import os
from openai import OpenAI

client = OpenAI(
    api_key=os.environ["OXYY_API_KEY"],
    base_url="https://api.oxyy.ai/v1"
)

response = client.images.generate(
    model="dall-e-3",
    prompt="A beautiful sunset over the ocean",
    n=1,
    size="1024x1024",
    quality="hd",
    style="vivid",
)
print(response.data[0].url)
import OpenAI from 'openai';

const client = new OpenAI({
  apiKey: process.env.OXYY_API_KEY,
  baseURL: 'https://api.oxyy.ai/v1'
});

const response = await client.images.generate({
  model: 'dall-e-3',
  prompt: 'A beautiful sunset over the ocean',
  n: 1,
  size: '1024x1024',
});
console.log(response.data[0].url);
curl https://api.oxyy.ai/v1/images/generations \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $OXYY_API_KEY" \
  -d '{
    "model": "dall-e-3",
    "prompt": "A beautiful sunset over the ocean",
    "n": 1,
    "size": "1024x1024",
    "response_format": "url"
  }'

Example response

Response
{
  "created": 1700000000,
  "data": [
    {
      "url": "https://api.oxyy.ai/storage/images/2026/01/abc123.png",
      "revised_prompt": "A beautiful sunset over a calm ocean..."
    }
  ],
  "usage": {
    "input_tokens": 12,
    "output_tokens": 1290,
    "total_tokens": 1302,
    "input_tokens_details": { "text_tokens": 12, "image_tokens": 0 },
    "output_tokens_details": {
      "image_tokens": 1290,
      "text_tokens": 0,
      "image_tokens_source": "catalog"
    },
    "cost": 0.04,
    "cost_details": { "currency": "USD", "image": 0.04 }
  }
}

output_tokens_details.image_tokens_source says where the image token count came from — provider, the model's own published table (catalog), or an estimate — because a charge you can question deserves to say how it was reached.

Editing & variations

POSThttps://api.oxyy.ai/v1/images/editsedit with a prompt
POSThttps://api.oxyy.ai/v1/images/variationsvary without a prompt

Both take the source image the same three ways as every other media endpoint — upload, URL or base64 — and both accept several reference images. See File inputs.

Asynchronous jobs

Send async: true and the endpoint answers 202 with a job instead of waiting. The job endpoints below behave exactly like the video ones.

GEThttps://api.oxyy.ai/v1/images/generations/{id}status
GEThttps://api.oxyy.ai/v1/images/generationsyour jobs
DELETEhttps://api.oxyy.ai/v1/images/generations/{id}cancel

Available models

13 models
Model
Model ID
CogView-4
cogview-4
FLUX.2 [dev]
flux-2-dev
FLUX.2 [klein] 4B
flux-2-klein-4b
FLUX.2 [klein] 9B
flux-2-klein-9b
GLM-Image
glm-image
Google: Nano Banana 2 Lite (Gemini 3.1 Flash Lite Image)
gemini-3.1-flash-lite-image
GPT Image 2
gpt-image-2
GPT-Image-2.5 Flare
gpt-image-2.5-flare
GPT-Image-2.5 Sunburst
gpt-image-2.5-sunburst
Grok Imagine Image
grok-imagine-image
Grok Imagine Image 2.0
grok-imagine-image-2.0
Muse Image 1.0
muse-image-1.0
Nano Banana 2
gemini-3.1-flash-image
Video API

Video generation

Generate video with Veo and the other video models in the catalogue.

POSThttps://api.oxyy.ai/v1/videos/generations
Video is always a job. This endpoint answers 202 Accepted with a job id — never a video. Poll the job until its status is completed, then read the file from result.data[0].url. A clip typically takes 30–120 seconds.

Parameters

ParameterTypeRequiredDescription
modelstringRequiredA video model id, e.g. veo-3, veo-3.1-fast.
promptstringRequired*What should happen in the shot. *Optional when you supply an image to animate.
imagefile|stringOptionalA starting frame — an upload, a URL or a data URL. Turns the request into image-to-video.
durationnumberOptionalClip length in seconds, 1–60. Providers clamp to what they support (Veo renders 4–8s) and you are billed for the length actually produced. Default 4
secondsstring|integerOptionalOpenAI's spelling of the same thing. One of:4812
resolutionstringOptionalOne of:480p720p1080p4k
sizestringOptionalWIDTHxHEIGHT, e.g. 1280x720. An alternative to resolution.
aspect_ratiostringOptionale.g. 16:9, 9:16.
negative_promptstringOptionalWhat to keep out of the shot.
generate_audiobooleanOptionalAsk for a soundtrack on the models that produce one.
person_generationstringOptionalProvider-side policy for depicting people.
seedintegerOptionalReproducibility, where supported.
nintegerOptionalClips to generate, 1–4. Not every provider returns more than one. Default 1
syncbooleanOptionalWait for the clip on the request instead of returning a job. Only for short clips — long generations will hit the request timeout. Default false
userstringOptionalA stable id for your own end user.

Submitting a job

import os, requests

headers = {
    "Authorization": f"Bearer {os.environ['OXYY_API_KEY']}",
    "Content-Type": "application/json",
}

# Video is asynchronous: this returns 202 with a job, not a video.
response = requests.post(
    "https://api.oxyy.ai/v1/videos/generations",
    headers=headers,
    json={
        "model": "veo-3",
        "prompt": "A serene lake with mountains, slow dolly in",
        "duration": 8,
        "resolution": "1080p",
        "aspect_ratio": "16:9",
    },
)
job = response.json()
print(response.status_code, job["id"], job["status"])
curl https://api.oxyy.ai/v1/videos/generations \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $OXYY_API_KEY" \
  -d '{
    "model": "veo-3",
    "prompt": "A serene lake with mountains",
    "duration": 8,
    "resolution": "1080p"
  }'
202 Accepted
HTTP/1.1 202 Accepted

{
  "id": "gen_01JQXY8M3K2T4V6W8Z",
  "object": "generation.job",
  "status": "queued",
  "type": "video",
  "model": "veo-3",
  "created_at": 1700000000,
  "expires_at": 1700086400,
  "_links": {
    "self": "/v1/videos/generations/gen_01JQXY8M3K2T4V6W8Z",
    "cancel": "/v1/videos/generations/gen_01JQXY8M3K2T4V6W8Z"
  }
}

Polling for the result

GEThttps://api.oxyy.ai/v1/videos/generations/{id}

Poll every five seconds or so. status moves through queued → processing → completed, with progress as a percentage; the terminal failures are failed and cancelled.

import os, time, requests

headers = { "Authorization": f"Bearer {os.environ['OXYY_API_KEY']}" }
job_id = "gen_..."  # the `id` the 202 returned

while True:
    job = requests.get(
        f"https://api.oxyy.ai/v1/videos/generations/{job_id}",
        headers=headers,
    ).json()

    if job["status"] == "completed":
        # The video lives under result.data[0].url
        print("Video:", job["result"]["data"][0]["url"])
        print("Charged:", job["usage"]["credits_consumed"])
        break
    if job["status"] in ("failed", "cancelled"):
        print("Failed:", job.get("error"))
        break

    print(f"{job['status']} — {job['progress']}%")
    time.sleep(5)
const headers = { Authorization: `Bearer ${process.env.OXYY_API_KEY}` };
const jobId = 'gen_...';

while (true) {
  const job = await fetch(
    `https://api.oxyy.ai/v1/videos/generations/${jobId}`,
    { headers }
  ).then((r) => r.json());

  if (job.status === 'completed') {
    console.log('Video:', job.result.data[0].url);
    break;
  }
  if (job.status === 'failed' || job.status === 'cancelled') {
    console.error('Failed:', job.error);
    break;
  }
  console.log(`${job.status} — ${job.progress}%`);
  await new Promise((r) => setTimeout(r, 5000));
}
# 1. Submit — the 202 body carries the job id.
curl -X POST https://api.oxyy.ai/v1/videos/generations \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $OXYY_API_KEY" \
  -d '{"model": "veo-3", "prompt": "A serene lake", "duration": 8}'

# 2. Poll until status is completed, failed or cancelled.
curl https://api.oxyy.ai/v1/videos/generations/gen_abc123 \
  -H "Authorization: Bearer $OXYY_API_KEY"

# 3. Cancel a job you no longer want.
curl -X DELETE https://api.oxyy.ai/v1/videos/generations/gen_abc123 \
  -H "Authorization: Bearer $OXYY_API_KEY"

Completed job

Response
{
  "id": "gen_01JQXY8M3K2T4V6W8Z",
  "object": "generation.job",
  "status": "completed",
  "type": "video",
  "model": "veo-3",
  "progress": 100,
  "created_at": 1700000000,
  "started_at": 1700000004,
  "completed_at": 1700000098,
  "expires_at": 1700086400,
  "result": {
    "created": 1700000098,
    "data": [
      {
        "url": "https://api.oxyy.ai/storage/videos/2026/01/abc123.mp4",
        "duration": 8,
        "resolution": "1080p"
      }
    ]
  },
  "usage": { "credits_consumed": 3.2 }
}

Failed job

Response
{
  "id": "gen_01JQXY8M3K2T4V6W8Z",
  "object": "generation.job",
  "status": "failed",
  "type": "video",
  "model": "veo-3",
  "progress": 30,
  "created_at": 1700000000,
  "error": {
    "message": "The video was filtered by the provider's safety system",
    "code": "CONTENT_FILTER"
  }
}

Managing jobs

GEThttps://api.oxyy.ai/v1/videos/generationsyour jobs; ?status=&limit=&offset=
DELETEhttps://api.oxyy.ai/v1/videos/generations/{id}cancel
POSThttps://api.oxyy.ai/v1/videos/image-to-videoanimate an image

A job is yours alone — another account's id answers 404, not 403. Jobs expire at expires_at; download the file before then or re-host it yourself.

Available models

7 models
Model
Model ID
CogVideoX-3
cogvideox-3
Dubbing v1
eleven_dubbing_v1
Dubbing v2
eleven_dubbing_v2
Gemini Omni Flash
gemini-omni-1.1-flash
Grok Imagine Video
grok-imagine-video
Grok Imagine Video 1.5
grok-imagine-video-1.5
Popular Special Effects Video Templates
glm-video-effects-agent
Audio API

Text to speech

ElevenLabs, OpenAI TTS and Gemini TTS, through the OpenAI speech endpoint. Music models are served here too.

POSThttps://api.oxyy.ai/v1/audio/speechreturns audio bytes

Parameters

ParameterTypeRequiredDescription
modelstringRequiredA TTS model id, e.g. tts-1-hd, eleven_v3, gemini-2.5-flash-tts.
inputstringRequiredThe text to speak. Up to 4,096 characters.
voicestringRequiredOn OpenAI models, a voice name (alloy, echo, fable, onyx, nova, shimmer). On ElevenLabs, a voice name from your library or a 20-character voice id — both are resolved for you.
response_formatstringOptionalThe audio container. One of:mp3opusaacflacwavpcm Default mp3
speednumberOptionalPlayback rate, 0.25–4. Default 1
instructionsstringOptionalDelivery direction for the models that accept it (e.g. gpt-4o-mini-tts).
stream_formatstringOptionalOne of:sseaudio
asyncbooleanOptionalReturn a job instead of waiting — useful for long input. Poll GET /v1/audio/speech/{id}. Default false
userstringOptionalA stable id for your own end user.

Code examples

import os
from pathlib import Path
from openai import OpenAI

client = OpenAI(
    api_key=os.environ["OXYY_API_KEY"],
    base_url="https://api.oxyy.ai/v1"
)

with client.audio.speech.with_streaming_response.create(
    model="tts-1-hd",
    voice="alloy",
    input="Hello! This is a text-to-speech test.",
    response_format="mp3",
    speed=1.0,
) as response:
    response.stream_to_file(Path("output.mp3"))
# The body is raw audio bytes — write it straight to a file.
curl https://api.oxyy.ai/v1/audio/speech \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $OXYY_API_KEY" \
  -d '{
    "model": "eleven_v3",
    "voice": "Rachel",
    "input": "Hello from Oxyy.",
    "response_format": "mp3"
  }' \
  --output speech.mp3

Response

audio/mpeg
// The response body is raw audio bytes, not JSON.
//
// HTTP/1.1 200 OK
// Content-Type: audio/mpeg
// Content-Length: 45321
// X-Request-Id: 8f2c...
//
// Write it straight to a file or pipe it to a player.
// response_format picks the container: mp3, opus, aac, flac, wav, pcm.

Available models

20 models
Model
Model ID
Eleven English v2 (Voice Changer)
eleven_english_sts_v2
Eleven Flash v2
eleven_flash_v2
Eleven Flash v2.5
eleven_flash_v2_5
Eleven Multilingual v2
eleven_multilingual_v2
Eleven Multilingual v2 (Voice Changer)
eleven_multilingual_sts_v2
Eleven Multilingual v2 (Voice Design)
eleven_multilingual_ttv_v2
Eleven Music v2
music_v2
Eleven Music v2.5
music_v2_5
Eleven v3
eleven_v3
Eleven v3 (Voice Design / Text to Voice)
eleven_ttv_v3
Eleven v3 Conversational
eleven_v3_conversational
ElevenAgents Speech Engine
eleven_speech_engine
Gemini 2.5 Flash TTS
gemini-2.5-flash-preview-tts
Gemini 2.5 Pro TTS
gemini-2.5-pro-preview-tts
Gemini 3.1 Flash TTS
gemini-3.1-flash-tts-preview
GPT-4o Mini TTS
gpt-4o-mini-tts
Grok Voice API — Text to Speech
grok-tts
Sound Effects v2
eleven_text_to_sound_v2
TTS-1
tts-1
TTS-1 HD
tts-1-hd
Audio API

Speech to text

Transcribe audio with timestamps, language detection and speaker diarization, in several output formats.

POSThttps://api.oxyy.ai/v1/audio/transcriptionstranscribe in the source language
POSThttps://api.oxyy.ai/v1/audio/translationstranscribe into English

Parameters

ParameterTypeRequiredDescription
modelstringRequiredAn STT model id, e.g. whisper-1, gpt-4o-transcribe, scribe_v1.
filefileRequired*The audio, as multipart/form-data. Containers: mp3, mp4, mpeg, mpga, m4a, wav, webm, ogg, flac.
file_urlstringRequired*A public URL to fetch instead of uploading. Up to 25 MB; private and loopback addresses are refused.
file_base64stringRequired*Base64 audio, with or without a data: prefix.
response_formatstringOptionalOne of:jsontextverbose_jsonsrtvttdiarized_json Default json
languagestringOptionalISO-639-1 code (en, ja, bn). Omit to auto-detect.
promptstringOptionalVocabulary or style hint — names, jargon, expected spelling.
temperaturenumberOptionalSampling temperature, 0–1.
timestamp_granularitiesarrayOptionalRequires verbose_json. One of:wordsegment
diarizebooleanOptionalLabel speakers, on the models that support it.
num_speakersintegerOptionalA hint for diarization.
*Exactly one of file, file_url or file_base64 is required. /v1/audio/translations takes the same parameters and returns English text.

Code examples

import os
from openai import OpenAI

client = OpenAI(
    api_key=os.environ["OXYY_API_KEY"],
    base_url="https://api.oxyy.ai/v1"
)

# Supported containers: mp3, mp4, mpeg, mpga, m4a, wav, webm, ogg, flac
with open("interview.mp3", "rb") as audio_file:
    response = client.audio.transcriptions.create(
        model="whisper-1",
        file=audio_file,
        response_format="verbose_json",
        timestamp_granularities=["segment"],
        language="en",
    )

print(response.text)
for seg in response.segments:
    print(f"{seg.start:.1f}s-{seg.end:.1f}s: {seg.text}")
import fs from 'fs';
import OpenAI from 'openai';

const client = new OpenAI({
  apiKey: process.env.OXYY_API_KEY,
  baseURL: 'https://api.oxyy.ai/v1'
});

const response = await client.audio.transcriptions.create({
  model: 'whisper-1',
  file: fs.createReadStream('interview.mp3'),
  response_format: 'verbose_json',
  language: 'en',
});

console.log(response.text);
# multipart upload
curl https://api.oxyy.ai/v1/audio/transcriptions \
  -H "Authorization: Bearer $OXYY_API_KEY" \
  -F "model=whisper-1" \
  -F "file=@interview.mp3" \
  -F "response_format=verbose_json" \
  -F "language=en"

# …or JSON with a hosted URL instead of an upload
curl https://api.oxyy.ai/v1/audio/transcriptions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $OXYY_API_KEY" \
  -d '{"model": "whisper-1", "file_url": "https://example.com/interview.mp3"}'
# Same inputs as /transcriptions; the transcript comes back in English.
curl https://api.oxyy.ai/v1/audio/translations \
  -H "Authorization: Bearer $OXYY_API_KEY" \
  -F "model=whisper-1" \
  -F "file=@bengali-interview.mp3" \
  -F "response_format=json"

Example response (verbose_json)

Response
{
  "task": "transcribe",
  "language": "en",
  "duration": 5.42,
  "text": "Hello, this is a transcription test.",
  "segments": [
    { "id": 0, "start": 0.0, "end": 2.1, "text": "Hello, this is" },
    { "id": 1, "start": 2.1, "end": 5.42, "text": "a transcription test." }
  ]
}

verbose_json is requested from the provider as sent. The one exception is the text-shaped formats: text, srt and vtt are rendered here from a timed result, so they work on every provider.

Available models

13 models
Model
Model ID
Gemini 3.5 Transcribe
gemini-3.5-transcribe
Gemini 3.5 Transcribe Live
gemini-3.5-transcribe-live
GLM-ASR-2512
glm-asr-2512
GPT-Live-Transcribe
gpt-live-transcribe
GPT-Realtime-Translate
gpt-realtime-translate
GPT-Realtime-Whisper
gpt-realtime-whisper
GPT-Transcribe
gpt-transcribe
Grok Voice API — Speech to Text
grok-stt
Muse Voice Transcribe 1.0
muse-voice-transcribe-1.0
Scribe v2
scribe_v2
Scribe v2 Medical
scribe_v2_medical
Scribe v2 Realtime
scribe_v2_realtime
Voice Isolator
eleven_voice_isolator
Embeddings API

Embeddings

Vectors for semantic search, clustering, recommendations and RAG.

POSThttps://api.oxyy.ai/v1/embeddings

Parameters

ParameterTypeRequiredDescription
modelstringRequiredAn embedding model id, e.g. text-embedding-3-large, gemini-embedding-001.
inputstring|arrayRequiredOne string, or a batch of up to 2,048. The batch is capped at 2,000,000 characters in total.
encoding_formatstringOptionalOne of:floatbase64 Default float
dimensionsintegerOptionalTruncate the vector, on models that support shortening.
userstringOptionalA stable id for your own end user.

Code examples

import os
from openai import OpenAI

client = OpenAI(
    api_key=os.environ["OXYY_API_KEY"],
    base_url="https://api.oxyy.ai/v1"
)

# `input` takes one string or a batch of up to 2048.
response = client.embeddings.create(
    model="text-embedding-3-large",
    input=["The quick brown fox", "jumps over the lazy dog"],
    encoding_format="float",
    dimensions=1024,
)

for item in response.data:
    print(item.index, len(item.embedding))
curl https://api.oxyy.ai/v1/embeddings \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $OXYY_API_KEY" \
  -d '{
    "model": "text-embedding-3-large",
    "input": "The quick brown fox",
    "encoding_format": "float"
  }'

Example response

Response
{
  "object": "list",
  "data": [
    {
      "object": "embedding",
      "index": 0,
      "embedding": [0.0023, -0.0091, 0.0156, -0.0042, "..."]
    }
  ],
  "model": "text-embedding-3-large",
  "usage": { "prompt_tokens": 8, "total_tokens": 8, "cost": 0.0000011 }
}

Available models

1 model
Model
Model ID
Gemini Embedding 2
google/gemini-embedding-2
File inputs

File inputs & uploads

Image editing, image-to-video and transcription all take a file. Every one of them accepts the same three methods, so you can pick whichever fits your architecture.

MethodContent-TypeFieldBest for
Uploadmultipart/form-dataimage / fileA file you already have on disk. The official SDKs do this for you.
URLapplication/jsonimage / file_urlA file you already host, or one uploaded to the asset store.
Base64application/jsonimage_base64 / file_base64Bytes you hold in memory, with no hosting step.
Several references at once. Image endpoints accept an array: images_base64 for base64, or images for data URLs, and a multipart request may carry up to ten files. The first is the primary reference; the rest are additional context for models that blend them.

The asset store

Upload once, reference many times. Useful when the same image feeds several requests, or when you would rather not resend megabytes of base64 on every call.

POSThttps://api.oxyy.ai/v1/assets/uploadmultipart, field: file
GEThttps://api.oxyy.ai/v1/assets/{id}metadata
GEThttps://api.oxyy.ai/v1/assetsyour assets
DELETEhttps://api.oxyy.ai/v1/assets/{id}delete
# 1. Upload once — the URL is reusable for 12 hours.
curl https://api.oxyy.ai/v1/assets/upload \
  -H "Authorization: Bearer $OXYY_API_KEY" \
  -F "file=@source.png"

# => {"asset_id":"...","url":"https://.../source.png","expires_at":...}

# 2. Reference that URL from any media request.
curl https://api.oxyy.ai/v1/images/edits \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $OXYY_API_KEY" \
  -d '{
    "model": "flux.1-kontext",
    "image": "https://api.oxyy.ai/storage/images/source.png",
    "prompt": "Change the background to a beach sunset"
  }'
import os, requests

headers = { "Authorization": f"Bearer {os.environ['OXYY_API_KEY']}" }

with open("source.png", "rb") as f:
    asset = requests.post(
        "https://api.oxyy.ai/v1/assets/upload",
        headers=headers,
        files={"file": ("source.png", f, "image/png")},
    ).json()

print(asset["asset_id"], asset["url"], asset["expires_at"])
Response
{
  "asset_id": "a1b2c3d4e5f6",
  "url": "https://api.oxyy.ai/storage/images/a1b2c3d4e5f6.png",
  "expires_at": 1700043200,
  "size": 184320,
  "mime_type": "image/png"
}
Assets expire after 12 hours. The store is a staging area for requests in flight, not file hosting. Anything you need to keep, keep yourself.

Limits & accepted types

CategoryMaximum per fileAccepted types
Image20 MBpng, jpeg, gif, webp
Audio50 MBmp3, wav, ogg, flac, m4a, mp4
Video100 MBmp4, webm, mov, avi
Document100 MBPDF and other documents, as a chat content part

A multipart request may carry up to 10 files and 100 MB per file overall; the per-category caps above are what each endpoint enforces, and an operator can set a lower cap on an individual model. A file_url on transcription is fetched with a 25 MB ceiling. Uploads are stored under a type derived from the content type, never from the filename you send — so scriptable formats such as SVG and HTML are refused outright.

Image editing

POSThttps://api.oxyy.ai/v1/images/edits
ParameterTypeRequiredDescription
modelstringRequiredAn image model that supports editing, e.g. flux.1-kontext.
promptstringRequiredThe edit to make.
imagefile|stringRequired*The source image — an upload, a URL or a data URL.
image_base64string|arrayRequired*Base64 source image(s).
images_base64arrayOptionalSeveral base64 references, in order.
maskfileOptionalA mask marking the region to change, on models that take one.
nintegerOptionalDefault 1
sizestringOptionalAs on image generation.
response_formatstringOptionalOne of:urlb64_json
*One of image, image_base64 or a multipart upload is required.
# Image edit — multipart upload straight from disk.
import os, requests

headers = { "Authorization": f"Bearer {os.environ['OXYY_API_KEY']}" }

response = requests.post(
    "https://api.oxyy.ai/v1/images/edits",
    headers=headers,
    files={"image": open("source.png", "rb")},
    data={
        "model": "flux.1-kontext",
        "prompt": "Change the background to a beach sunset",
        "n": "1",
        "size": "1024x1024",
    },
)
print(response.json()["data"][0]["url"])
import fs from 'fs';
import OpenAI from 'openai';

const client = new OpenAI({
  apiKey: process.env.OXYY_API_KEY,
  baseURL: 'https://api.oxyy.ai/v1'
});

const response = await client.images.edit({
  model: 'flux.1-kontext',
  image: fs.createReadStream('source.png'),
  prompt: 'Change the background to a beach sunset',
  n: 1,
  size: '1024x1024',
});

console.log(response.data[0].url);
curl https://api.oxyy.ai/v1/images/edits \
  -H "Authorization: Bearer $OXYY_API_KEY" \
  -F "model=flux.1-kontext" \
  -F "image=@source.png" \
  -F "prompt=Change the background to a beach sunset" \
  -F "n=1" \
  -F "size=1024x1024"
# Base64 — no upload step, no hosting. Note the two spellings:
# `image_base64` for one reference, `images_base64` for several.
curl https://api.oxyy.ai/v1/images/edits \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $OXYY_API_KEY" \
  -d '{
    "model": "flux.1-kontext",
    "prompt": "Blend these two scenes",
    "images_base64": ["iVBORw0KGgo...", "data:image/png;base64,iVBORw0..."]
  }'
Response
{
  "created": 1700000000,
  "data": [
    { "url": "https://api.oxyy.ai/storage/images/2026/01/edited_abc123.png" }
  ],
  "usage": { "input_tokens": 9, "output_tokens": 1290, "cost": 0.04 }
}

Image to video

POSThttps://api.oxyy.ai/v1/videos/image-to-video

Same parameters as video generation, with the image required rather than optional. It is a job like every other video request — poll it the same way. /v1/videos/generations also accepts an image and behaves identically.

# Image-to-video — the starting frame plus a motion prompt.
import os, requests

headers = { "Authorization": f"Bearer {os.environ['OXYY_API_KEY']}" }

response = requests.post(
    "https://api.oxyy.ai/v1/videos/image-to-video",
    headers=headers,
    files={"image": open("photo.jpg", "rb")},
    data={
        "model": "veo-3",
        "prompt": "Slowly pan across the scene",
        "duration": "8",
        "resolution": "1080p",
    },
)
job = response.json()
print(job["id"], job["status"])
curl https://api.oxyy.ai/v1/videos/image-to-video \
  -H "Authorization: Bearer $OXYY_API_KEY" \
  -F "model=veo-3" \
  -F "image=@photo.jpg" \
  -F "prompt=Slowly pan across the scene" \
  -F "duration=8" \
  -F "resolution=1080p"

Audio transcription

See Speech to text for the full parameter list. All three input methods apply.

Anthropic SDK

Anthropic SDK

Claude models can be called with Anthropic's official SDK through the Messages API. Set the base URL to https://api.oxyy.ai without /v1 — the SDK adds /v1/messages itself.

POSThttps://api.oxyy.ai/v1/messages
POSThttps://api.oxyy.ai/v1/messages/count_tokensfree; does not spend your rate limit

Authenticate with your Oxyy API key in the x-api-key header, which is what the SDK's api_key option sends, or as Authorization: Bearer. As on Anthropic's own API, max_tokens is required.

Claude models only. Any other model sent to /v1/messages is refused with a 400, in Anthropic's own error shape so your SDK raises the exception it expects. Use the OpenAI SDK for every other model.

Code examples

# pip install anthropic
import anthropic, os

client = anthropic.Anthropic(
    api_key=os.environ["OXYY_API_KEY"],
    base_url="https://api.oxyy.ai",  # no /v1: the SDK adds it
)

message = client.messages.create(
    model="claude-sonnet-4.5",
    max_tokens=1024,
    system="Be concise.",
    messages=[{"role": "user", "content": "Hello!"}],
)

print(message.content[0].text)
// npm install @anthropic-ai/sdk
import Anthropic from '@anthropic-ai/sdk';

const client = new Anthropic({
  apiKey: process.env.OXYY_API_KEY,
  baseURL: 'https://api.oxyy.ai', // no /v1: the SDK adds it
});

const message = await client.messages.create({
  model: 'claude-sonnet-4.5',
  max_tokens: 1024,
  messages: [{ role: 'user', content: 'Hello!' }],
});

console.log(message.content[0].text);
curl https://api.oxyy.ai/v1/messages \
  -H "Content-Type: application/json" \
  -H "x-api-key: $OXYY_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -d '{
    "model": "claude-sonnet-4.5",
    "max_tokens": 1024,
    "messages": [{"role": "user", "content": "Hello!"}]
  }'
# cache_control survives translation, per block — a long system
# prompt is written once and read at the cache rate afterwards.
message = client.messages.create(
    model="claude-sonnet-4.5",
    max_tokens=1024,
    system=[{
        "type": "text",
        "text": long_style_guide,
        "cache_control": {"type": "ephemeral"},
    }],
    thinking={"type": "enabled", "budget_tokens": 2048},
    messages=[{"role": "user", "content": "Review this draft."}],
)

print(message.usage.cache_read_input_tokens)

Supported features

FeatureStatusNotes
system as a string or a block arraySupportedPer-block cache_control is preserved, so a long system prompt is cached rather than re-billed
max_tokensSupportedRequired, exactly as on Anthropic's own API
temperature, top_p, top_k, stop_sequencesSupported
streamSupportedNative Anthropic events: message_start, content_block_delta, message_delta, message_stop
thinkingSupportedForwarded verbatim — enabled, adaptive and disabled all reach the model
tools, tool_choiceSupportedauto, any, none and {type:"tool", name}. disable_parallel_tool_use is honoured.
Server tools (web_search, code_execution, …)SupportedPassed to the provider untouched; whether one runs depends on the upstream serving that model
output_config.formatSupportedA json_schema format becomes a structured output; other formats are reported as dropped
metadata, service_tier, betas / anthropic_betaSupportedbetas becomes the anthropic-beta header
container, mcp_servers, context_management, inference_geoSupportedForwarded as sent
POST /v1/messages/count_tokensSupportedAnswered from this gateway's own tokenizer; it does not spend your rate limit
Legacy Text Completions (prompt, max_tokens_to_sample)Not supportedRetired by Anthropic. Refused with a 400 that names the Messages shape to send instead.
Non-Claude modelsNot supportedRefused with a 400. Use the OpenAI SDK for every other model.
Undocumented top-level fieldsNot supportedDropped rather than forwarded, so a validating upstream cannot 400 on a field it has never seen
Google GenAI SDK

Google GenAI SDK

Gemini models can be called with Google's google-genai (Python) and @google/genai (JavaScript) SDKs. Set the base URL in the SDK's HTTP options (http_options in Python, httpOptions in JavaScript) to https://api.oxyy.ai; the SDK adds /v1beta itself.

POSThttps://api.oxyy.ai/v1beta/models/{model}:generateContent
POSThttps://api.oxyy.ai/v1beta/models/{model}:streamGenerateContent
POSThttps://api.oxyy.ai/v1beta/models/{model}:countTokensfree
GEThttps://api.oxyy.ai/v1beta/modelsmodels served over this SDK

The SDK's api_key is sent as the x-goog-api-key header; a ?key=query parameter or Authorization: Bearer works too.

Gemini models only. Any other model is refused with a 400 in Google's error shape. Imagen and Veo are not served here either — use image generation and video generation for them.

Code examples

# pip install google-genai
import os
from google import genai
from google.genai import types

client = genai.Client(
    api_key=os.environ["OXYY_API_KEY"],
    http_options=types.HttpOptions(base_url="https://api.oxyy.ai"),
)

response = client.models.generate_content(
    model="gemini-2.5-flash",
    contents="Hello!",
    config=types.GenerateContentConfig(
        system_instruction="Be concise.",
        temperature=0.7,
        max_output_tokens=1024,
    ),
)

print(response.text)
// npm install @google/genai
import { GoogleGenAI } from '@google/genai';

const ai = new GoogleGenAI({
  apiKey: process.env.OXYY_API_KEY,
  httpOptions: { baseUrl: 'https://api.oxyy.ai' }
});

const response = await ai.models.generateContent({
  model: 'gemini-2.5-flash',
  contents: 'Hello!'
});

console.log(response.text);
curl https://api.oxyy.ai/v1beta/models/gemini-2.5-flash:generateContent \
  -H "Content-Type: application/json" \
  -H "x-goog-api-key: $OXYY_API_KEY" \
  -d '{"contents": [{"parts": [{"text": "Hello!"}]}]}'

Supported features

FeatureStatusNotes
:generateContent, :streamGenerateContentSupportedStreaming is what ?alt=sse and the SDK's generate_content_stream use
:countTokensSupportedAnswered from this gateway's tokenizer; exempt from your rate limit
GET /v1beta/modelsSupportedLists only the models offered over this SDK. Requires your API key.
systemInstructionSupported
generationConfigSupportedtemperature, topP, topK, maxOutputTokens, stopSequences, responseMimeType, responseSchema, thinkingConfig
tools / functionDeclarationsSupportedBoth parameters and parametersJsonSchema, in camelCase or snake_case — the Python SDK sends snake_case
safetySettingsSupportedForwarded to the provider
Inline and file data partsSupportedinlineData (base64) and fileData for images, audio, video and documents
Non-Gemini modelsNot supportedRefused with a 400 in Google's own error shape
Imagen and Veo through this SDKNot supportedUse /v1/images/generations and /v1/videos/generations
Libraries

SDKs & libraries

Oxyy speaks the OpenAI wire format, so the official OpenAI SDK in any language works unchanged. There is no Oxyy SDK to install.

LanguagePackageInstall
Pythonopenaipip install openai
JavaScript / TypeScriptopenainpm install openai
PHPopenai-php/clientcomposer require openai-php/client
Gosashabaranov/go-openaigo get github.com/sashabaranov/go-openai
Rubyruby-openaigem install ruby-openai
Java / Kotlincom.openai:openai-javaMaven / Gradle
C#OpenAIdotnet add package OpenAI

Frameworks built on the OpenAI client — LangChain, LlamaIndex, the Vercel AI SDK, PydanticAI, Instructor — work by setting the same base URL in their OpenAI provider.

Vendor SDKs

Claude and Gemini can also be called with their vendor's own SDK: see Anthropic SDK and Google GenAI SDK. A vendor SDK serves only that vendor's models; the OpenAI SDK reaches every model.

Forgiving base URLs. Each vendor SDK appends its own version prefix, which people get wrong in two predictable ways. Both are absorbed rather than 404'd: a base URL with no prefix (/messages, /responses) and one where the prefix was doubled (/v1/v1/messages, /v1beta/v1beta/…) reach the same pipeline with the same billing.

Grok (xAI) and Llama (Meta)

xai-sdk is not supported. xAI's official xai-sdk package speaks gRPC, which this REST API does not serve. Call Grok models with the OpenAI SDK and base_url set to https://api.oxyy.ai/v1.

Meta Llama models are also called through the OpenAI SDK, with chat completions or the Responses API. There is no separate Llama SDK surface.

Differences from other gateways

FieldOxyy
provider (root)Not returned. The upstream that served a request is intentionally not disclosed.
usage.costThe amount charged for the request, in USD — on every response, streaming included.
usage.is_byokAlways false: every request is served on Oxyy's own provider keys.
models / route / provider (request)Not accepted. Fallback and provider choice are handled for you; send one model.
transforms, pluginsNot accepted. Nothing is injected into or rewritten in your prompt.
Limits

Rate limits

Limits are set by your tier and apply per API key. Four things are counted independently: requests per minute, tokens per minute, per-modality daily caps, and how many requests you may have in flight at once. Exceeding any of them is a 429.

Response headers
HTTP/1.1 200 OK
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 587
X-RateLimit-Reset: 1700000060
X-RateLimit-Limit-Tokens: 2000000
X-RateLimit-Remaining-Tokens: 1984210
X-Request-Id: 9c1f4e7a-2b8d-4e31-9a0c-5f7b1d2e3a44

// On a 429 the same headers are present, plus:
Retry-After: 43
HeaderMeaning
X-RateLimit-LimitRequests per minute allowed on your tier
X-RateLimit-RemainingRequests left in the current minute
X-RateLimit-ResetUnix seconds at which the request window resets
X-RateLimit-Limit-TokensTokens per minute allowed, when your tier caps them
X-RateLimit-Remaining-TokensTokens left in the current window
Retry-AfterSeconds to wait. Sent on every 429; honour it rather than retrying at once.
X-Request-IdThe id this request is recorded under. Quote it in any support request.
Back off politely. Every 429 carries Retry-After in seconds. The official SDKs already honour it; if you call the API directly, wait that long rather than retrying immediately — a tight retry loop makes the limit last longer. Your current limits for a given model are in GET /v1/models/{model}.
Idempotency

Idempotency

A network timeout does not tell you whether the work happened. Send an Idempotency-Key and a retry returns the original answer instead of generating — and charging for — a second one.

# Same key + same body within 24h => the stored reply, charged once.
# The replay carries `idempotent-replay: true`.
curl https://api.oxyy.ai/v1/images/generations \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $OXYY_API_KEY" \
  -H "Idempotency-Key: order-4417-thumbnail" \
  -d '{"model": "dall-e-3", "prompt": "A red bicycle"}'
response = client.images.generate(
    model="dall-e-3",
    prompt="A red bicycle",
    extra_headers={"Idempotency-Key": "order-4417-thumbnail"},
)
RuleDetail
Where it appliesEvery billable POST: chat completions, responses, messages, images, audio, video and embeddings.
How longA finished reply is replayable for 24 hours.
Same key, same bodyThe stored reply is returned with idempotent-replay: true, and you are charged once.
Same key, different bodyRefused with 409. Use a new key.
Still in flightRefused with 409 while the first attempt is running. Retry shortly.
What is never storedAuthentication failures, rate-limit refusals and server errors — so a retry after one of those really does retry.
StreamingNot covered: a stream cannot be replayed, so the header is ignored on stream: true.
ScopeKeys are scoped to the credential that sent them, so two customers cannot collide on the same key.
Errors

Errors

Errors use standard HTTP status codes and the OpenAI error envelope, so the SDKs raise the exception classes you already handle. error.param names the offending field when there is one.

Response
{
  "error": {
    "message": "Invalid API key provided",
    "type": "authentication_error",
    "param": null,
    "code": "invalid_api_key"
  }
}
StatusTypeMeaningWhat to do
400invalid_request_errorA parameter is missing, malformed or outside its range. error.param names it.Fix the request. Retrying will not help.
401authentication_errorMissing, malformed, disabled or unknown API key.Check the Authorization header and the key's status.
402insufficient_quotaThe account has no credit left for this request.Top up. Do not retry — the balance will not change on its own.
403permission_errorThe key or tier may not use this model, endpoint or SDK.Use a model your tier allows, or a key with the right scope.
404not_found_errorNo such model, job or asset.Check the id against GET /v1/models.
408timeout_errorThe generation exceeded the budget for this request.Retry, or ask for a shorter output.
409invalid_request_errorAn Idempotency-Key was reused with a different body, or a job is already in flight.Use a new key, or resend the identical body.
413invalid_request_errorThe upload or request body is over the limit.See file size limits.
422invalid_request_errorThe request is well formed but the values cannot be served together.Read the message; it names the conflict.
429rate_limit_errorRequests per minute, tokens per minute, a daily cap, or too many concurrent requests.Back off for Retry-After seconds. See rate limits.
500server_errorAn unexpected fault in the gateway.Retry with backoff. Quote X-Request-Id if it persists.
501server_errorThe provider adapter does not implement this capability for this model.Use a different model for that modality.
502provider_errorThe upstream provider returned something unusable.Retry; routing will usually pick a different provider.
503service_unavailableNo provider can serve this model right now, or the gateway is shedding load.Retry with backoff, or use another model.
504timeout_errorThe upstream did not answer in time.Retry.

Handling them

import openai

try:
    response = client.chat.completions.create(model="gpt-5", messages=msgs)
except openai.RateLimitError as e:
    # 429 — honour Retry-After rather than retrying immediately.
    wait = int(e.response.headers.get("retry-after", 60))
except openai.APIStatusError as e:
    if e.status_code == 402:
        # Out of credits — top up, do not retry.
        ...
    print(e.status_code, e.body["error"]["code"])
Retry 429, 500, 502, 503 and 504. Do not retry 400, 401, 402, 403, 404 or 409. The first group is transient; the second will keep failing until something on your side changes. Errors raised after a stream has started arrive as an error frame inside the stream, because the HTTP status was already sent.
Getting help. Every error response carries X-Request-Id. Quote it and we can find the exact request — without it, we cannot.

Models and providers you can call

Every model below works with the quickstart above — same endpoint, same key. Open one for its exact id, pricing and limits.

AI providers

Z.ai models and pricing30OpenAI models and pricing28Google models and pricing25ElevenLabs models and pricing19xAI models and pricing14Anthropic models and pricing11Meta models and pricing9Black Forest Labs models and pricing3DeepSeek models and pricing2Qwen (Alibaba) models and pricing1

Popular models

  • chat-latest
  • Claude Fable 5
  • Claude Fable 5.1
  • Claude Haiku 4.5
  • Claude Opus 4.5
  • Claude Opus 4.6
  • Claude Opus 4.7
  • Claude Opus 4.8
  • Claude Opus 5
  • Claude Sonnet 4.5
  • Claude Sonnet 4.6
  • Claude Sonnet 5
  • DeepSeek-V4.1-Flash
  • DeepSeek-V4-Pro-0813
  • Gemini 2.5 Flash
  • Gemini 2.5 Flash-Lite
  • Gemini 2.5 Flash TTS
  • Gemini 2.5 Pro
  • Gemini 2.5 Pro TTS
  • Nano Banana 2
  • Gemini 3.1 Flash-Lite
  • Google: Nano Banana 2 Lite (Gemini 3.1 Flash Lite Image)
  • Gemini 3.1 Flash TTS
  • Gemini 3.1 Pro
All AI models All AI providers Full price list
oxyy.ai

One endpoint for every major model. The OpenAI, Anthropic and Gemini SDKs work unchanged.

Product

  • Models
  • Providers
  • Pricing
  • Status
  • Startup program

Company

  • About
  • Blog
  • Contact
  • Terms of Service
  • Privacy Policy
  • Refund Policy
  • Cookie Policy

Developer

  • Documentation
  • Quickstart
  • SDKs
  • Model catalog
  • AI providers

Connect

  • Telegram
  • Discord
  • WhatsApp
© 2026 Oxyy.ai. All rights reserved.StatusAbout