Documentation
myopenai is an OpenAI-compatible API gateway with hard per-key spend caps. Existing OpenAI SDK code works after swapping the base_url and the API key.
Quickstart
- Create an account and add credit from the billing page.
- Create an API key. Optionally set a dollar cap and reset window (daily, monthly or total).
- Set the
base_urlin your code tohttps://myopenai.site/v1.
SDK examples
Python
from openai import OpenAI
client = OpenAI(
api_key="mo_live_YOUR_KEY",
base_url="https://myopenai.site/v1",
)
response = client.chat.completions.create(
model="claude-sonnet-5",
messages=[{"role": "user", "content": "Hello"}],
max_tokens=512,
)
print(response.choices[0].message.content)Python — streaming
stream = client.chat.completions.create(
model="claude-sonnet-5",
messages=[{"role": "user", "content": "Write a haiku"}],
stream=True,
stream_options={"include_usage": True}, # final chunk carries token usage
)
for chunk in stream:
if chunk.choices and chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="")Python — tool calling
import json
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": "What is the weather in Berlin?"}]
response = client.chat.completions.create(
model="claude-sonnet-5", messages=messages, tools=tools,
)
message = response.choices[0].message
if message.tool_calls:
messages.append(message)
for call in message.tool_calls:
args = json.loads(call.function.arguments)
messages.append({
"role": "tool",
"tool_call_id": call.id,
"content": get_weather(**args), # your function
})
response = client.chat.completions.create(
model="claude-sonnet-5", messages=messages, tools=tools,
)
print(response.choices[0].message.content)Node.js
import OpenAI from 'openai';
const client = new OpenAI({
apiKey: 'mo_live_YOUR_KEY',
baseURL: 'https://myopenai.site/v1',
});
const response = await client.chat.completions.create({
model: 'claude-sonnet-5',
messages: [{ role: 'user', content: 'Hello' }],
});
console.log(response.choices[0].message.content);curl
curl https://myopenai.site/v1/chat/completions \
-H "Authorization: Bearer mo_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-sonnet-5",
"messages": [{"role": "user", "content": "Hello"}]
}'Models & pricing
Prices are per million tokens in US dollars and are what your credit is charged. The same list is available to SDKs from GET /v1/models.
Loading current models and prices…
Endpoints & parameters
POST /v1/chat/completions— chat completions, streaming and non-streaming.GET /v1/modelsandGET /v1/models/{model}— the models your key can use.
Tool calling works with the standard OpenAI parameters: tools (function tools), tool_choice (auto, required, none or a named function), tool role messages, and tool_calls deltas when streaming. With tool_choice: "none" the model answers in text and earlier tool calls in the conversation are passed to it as text. Claude models may return several tool calls even with parallel_tool_calls: false.
So that a request never silently does something other than what you asked, these return 400 instead of being ignored:
- the deprecated
functionsandfunction_callparameters (usetools) - image or other non-text message content
- for Claude models:
ngreater than 1,logprobs, andresponse_formatother than text
temperature and top_p are not accepted by Claude 5 models and are ignored. stop takes up to 4 sequences; whitespace-only sequences are ignored. developer messages are treated as system messages.
Spend caps, holds and HTTP 402
- Caps are per key, not per account: one key per project gives one budget per project.
- Before each call the gateway places a hold: an upper bound on what the call can cost, from the request size and its
max_tokens. The hold is checked against both your wallet and the key’s cap. If either cannot cover it, the request is refused with HTTP 402 and the upstream model is never called. - After the call only the tokens the model reports are charged and the rest of the hold is released. If a stream is interrupted before the model reports usage, the hold is charged.
- If you omit
max_tokens, the default below is applied and sent upstream, so no response can outgrow its hold. Set a smallermax_tokensfor smaller holds. - Windows:
dayresets at 00:00 UTC,monthon the 1st at 00:00 UTC,totalnever resets. - You can change a key’s cap at any time on the keys page.
The 402 body is OpenAI-shaped; amounts are integer micro-dollars (1 USD = 1,000,000 micros):
HTTP/2 402
{
"error": {
"message": "api key spend cap exceeded: cap_micros=5000000 spent_micros=4990000 required_micros=57036 cap_window=month",
"type": "cap_exceeded",
"code": "cap_exceeded"
}
}Handle it by catching the 402 status (the OpenAI SDKs raise an API status error) and alerting the user or pausing automated requests. insufficient_funds means the wallet needs credit; cap_exceeded means the key’s budget for the window is spent.
Limits
Loading current limits…
Errors
Every error has the OpenAI shape {"error": {"message", "type", "code"}}.
| Status | Type | Meaning |
|---|---|---|
| 400 | invalid_request_error | Malformed body, missing model, unpriced model, or a parameter the model does not support. The message names the problem. |
| 401 | invalid_request_error | Missing, unknown or revoked API key. |
| 402 | insufficient_funds | The wallet cannot cover the hold for this request. Add credit. |
| 402 | cap_exceeded | The hold would push the key past its spend cap in the current window. |
| 404 | model_not_found | The model id is not offered. List models with GET /v1/models. |
| 413 | invalid_request_error | Request body larger than the limit below. |
| 429 | rate_limit_error | Per-key rate or concurrency limit, or upstream throttling. Honour Retry-After. |
| 502 / 503 / 504 | api_error | Upstream failure, unavailability or timeout. Nothing is charged; retry with backoff. |