OpenGray API
Version 1 · Last updated September 24, 2026
OpenGray exposes a single OpenAI-compatible HTTP API in front of frontier language models. If your code already talks to the OpenAI Chat Completions API, you only need to change the base URL and the key.
| Base URL | https://open-gray.com/api/v1 |
| Auth scheme | Authorization: Bearer sk-og-v1-... |
| Content type | application/json |
| Billing unit | Credits (100 credits = US$1.00) |
Authentication
Every request must carry a bearer token issued from the API keys page. Keys are shown once at creation and stored only as a SHA-256 hash, so keep a copy in your secret manager. A key can be revoked at any time and can optionally carry a credit spend limit.
Quickstart
Create an account, add credits, then send your first request:
curl https://open-gray.com/api/v1/chat/completions \
-H "Authorization: Bearer sk-og-v1-..." \
-H "Content-Type: application/json" \
-d '{
"model": "claude-fable-5",
"messages": [
{ "role": "user", "content": "Summarise our Q3 revenue drivers." }
]
}'
from openai import OpenAI
client = OpenAI(
base_url="https://open-gray.com/api/v1",
api_key="sk-og-v1-...",
)
response = client.chat.completions.create(
model="claude-fable-5",
messages=[
{"role": "user", "content": "Summarise our Q3 revenue drivers."}
],
)
print(response.choices[0].message.content)
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://open-gray.com/api/v1",
apiKey: "sk-og-v1-...",
});
const response = await client.chat.completions.create({
model: "claude-fable-5",
messages: [
{ role: "user", content: "Summarise our Q3 revenue drivers." },
],
});
console.log(response.choices[0].message.content);
<?php
$payload = [
'model' => 'claude-fable-5',
'messages' => [
['role' => 'user', 'content' => 'Summarise our Q3 revenue drivers.'],
],
];
$ch = curl_init('https://open-gray.com/api/v1/chat/completions');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer sk-og-v1-...',
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode($payload),
]);
$body = json_decode(curl_exec($ch), true);
echo $body['choices'][0]['message']['content'];
Chat completions
POST /chat/completions
Request fields:
| Field | Type | Description |
|---|---|---|
model | string | Required. A slug from /models, for example claude-fable-5. |
messages | array | Required. Standard chat messages with role and content. Content may be a string or an array of text and image parts. |
stream | boolean | Stream the response as server-sent events. Defaults to false. |
max_tokens | integer | Upper bound on generated tokens. |
temperature | number | Sampling temperature, where the model supports it. |
reasoning_effort | string | low, medium, high, xhigh or max. Higher effort costs more output tokens. |
tools, tool_choice | array, string | Function calling, passed through unchanged. |
response_format | object | Use {"type":"json_object"} or a JSON schema for structured output. |
A successful response mirrors the OpenAI shape and adds the credits consumed:
{
"id": "gen-1a2b3c",
"object": "chat.completion",
"created": 1785794208,
"model": "claude-fable-5",
"choices": [
{
"index": 0,
"message": { "role": "assistant", "content": "..." },
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 412,
"completion_tokens": 180,
"total_tokens": 592,
"cost_credits": 1.312
}
}
x-opengray-credits-charged and x-opengray-balance response headers carry the same figures.Streaming
Set "stream": true to receive text/event-stream frames. Each frame is a
data: line containing a chunk object, and the stream terminates with
data: [DONE]. The final chunk before the terminator includes the
usage block with cost_credits.
curl -N https://open-gray.com/api/v1/chat/completions \
-H "Authorization: Bearer sk-og-v1-..." \
-H "Content-Type: application/json" \
-d '{"model":"claude-fable-5","stream":true,"messages":[{"role":"user","content":"Count to five."}]}'
Credits are debited when the stream completes. If a client disconnects mid-stream, the tokens already generated upstream are still billed.
List models
GET /models returns the catalogue with pricing expressed in credits per token.
{
"object": "list",
"data": [
{
"id": "claude-fable-5",
"object": "model",
"owned_by": "Anthropic",
"context_length": 1000000,
"pricing": { "prompt_credits": "0.001", "completion_credits": "0.005" }
}
]
}
Key and balance
GET /key returns the calling key's metadata and the account balance. Use it for
preflight checks in long-running jobs.
{
"data": {
"label": "Production",
"balance_credits": 842.51,
"balance_usd": 8.4251,
"limit_credits": null,
"usage_credits": 57.49
}
}
Billing behaviour
- Charges are derived from the generation's real token counts, never from an estimate.
- Cached input tokens bill at the model's cache-read rate automatically.
- Requests are refused with
402when the balance is at or below the minimum threshold. - Failed upstream requests are logged but not charged.
- Every charge appears on the activity and transactions pages within seconds.
Errors
Errors use the OpenAI envelope: {"error": {"message": "...", "type": "...", "code": 400}}.
| Status | Type | Meaning |
|---|---|---|
400 | invalid_request_error | Malformed JSON, missing messages or an unknown model. |
401 | authentication_error | Missing, malformed or revoked API key. |
402 | insufficient_credits | Account balance or key limit exhausted. |
403 | account_suspended | The owning account is suspended. |
404 | model_not_found | The requested model is not in the catalogue. |
429 | rate_limit_error | Too many requests; retry with backoff. |
502 | upstream_error | The provider returned an error or timed out. Not billed. |
Limits
- Request body limit: 8 MB.
- Non-streaming timeout: 300 seconds. Streaming timeout: 600 seconds.
- Concurrency is not capped per account; sustained abuse is rate limited per key.
- Keys per account: unlimited.
Migrating
From OpenAI: set base_url to https://open-gray.com/api/v1, swap the key, and use an OpenGray model slug.
From OpenRouter: the request and response shapes match. Replace vendor-prefixed slugs such as anthropic/claude-fable-5 with claude-fable-5; the prefixed form is accepted too.
Anything unclear? Write to contact@open-gray.com and we will answer.