Quickstart
Create a project API key, list the models that key can use, then send a request through the Gateway with any of three SDK formats.
Create a project API key
Sign in to the console, create or choose an organization, workspace, and project, then issue an API key from that project. The key inherits the project's allowed model list and credit balance. Keys start with atp- and are 92 characters long. The full secret is shown once at creation — store it somewhere safe.
Point your SDK at the Gateway
The Gateway accepts the OpenAI, Anthropic, and Google GenAI SDKs using each SDK's default authentication header — no wrapper library needed. See Authentication for the three accepted key locations.
# OpenAI SDK
from openai import OpenAI
client = OpenAI(
base_url="https://api.atptoken.ai/v1",
api_key="atp-..."
)
# Anthropic SDK
from anthropic import Anthropic
client = Anthropic(
base_url="https://api.atptoken.ai",
api_key="atp-..."
)
# Google GenAI SDK (Gemini) — uses x-goog-api-key by default
from google import genai
client = genai.Client(
api_key="atp-...",
http_options={"base_url": "https://api.atptoken.ai"}
)Discover allowed models
curl https://api.atptoken.ai/v1/models \
-H "Authorization: Bearer atp-..."Use one of the returned model IDs in the request body. If a model is not enabled for the project, the Gateway returns 403 before reaching any provider.
Make a request
curl https://api.atptoken.ai/v1/chat/completions \
-H "Authorization: Bearer atp-..." \
-H "Content-Type: application/json" \
-d '{
"model": "<model from GET /v1/models>",
"messages": [{"role": "user", "content": "hi"}]
}'/v1/modelsList available models
Lists every model available on the platform — the same catalog for every key, not only the models enabled for your project. Use it to get exact, current model IDs instead of copying names from a marketing page or provider dashboard.
GET /v1/models is not scoped to your project. Which of these models a key may actually call is set by its project's allowed list and enforced at request time — calling a model that isn't enabled returns 403.
Response
{
"object": "list",
"data": [
{ "id": "claude-sonnet-4-6", "object": "model", "created": 1700000000, "owned_by": "llm-gateway" },
{ "id": "gpt-5.4", "object": "model", "created": 1700000000, "owned_by": "llm-gateway" }
]
}How model access works
Three accepted key locations
The Gateway accepts a project API key in any of the locations below. Use whichever your SDK sends by default — there's no need to override headers. The Gateway strips the key from the request before forwarding to the provider.
| Location | Used by |
|---|---|
| Authorization: Bearer atp-… | OpenAI SDK, Anthropic SDK, curl |
| x-goog-api-key: atp-… | Google GenAI SDK (recommended for Gemini) |
| ?key=atp-… | Gemini REST clients. Logs scrub this query param — prefer the header form when possible. |
If none of the three are present → 401. If the key is disabled or expired → 401.
Coding agents (Claude Code, Codex)
Any agent that speaks the Anthropic or OpenAI wire format can run on a project key — point it at the Gateway base URL and use a model from GET /v1/models.
Claude Code
Set the base URL (no /v1 — Claude Code appends /v1/messages itself) and send the key as the auth token. Clear ANTHROPIC_API_KEY so it doesn't take precedence.
export ANTHROPIC_BASE_URL="https://api.atptoken.ai"
export ANTHROPIC_AUTH_TOKEN="atp-..."
export ANTHROPIC_API_KEY=""
export ANTHROPIC_MODEL="claude-sonnet-4-6" # any model from GET /v1/models
claudeOr set the same values under the env block of ~/.claude/settings.json:
{
"env": {
"ANTHROPIC_BASE_URL": "https://api.atptoken.ai",
"ANTHROPIC_AUTH_TOKEN": "atp-...",
"ANTHROPIC_API_KEY": ""
},
"model": "claude-sonnet-4-6"
}Codex CLI
Add the Gateway as a custom OpenAI-compatible provider in ~/.codex/config.toml (base URL includes /v1), then export the key named by env_key.
# ~/.codex/config.toml
model = "gpt-5.4" # any model from GET /v1/models
model_provider = "atp"
[model_providers.atp]
name = "ATP"
base_url = "https://api.atptoken.ai/v1"
env_key = "ATP_API_KEY"
wire_api = "chat"export ATP_API_KEY="atp-..."
codexBoth tools authenticate with Authorization: Bearer atp-…. The model must be enabled for the key's project, or the Gateway returns 403.
Set up your organization
Everything in the console hangs off a four-level hierarchy. You create an organization once, then nest workspaces, projects, and keys inside it.
| Level | What it holds |
|---|---|
| Organization | The billing and team boundary. Either Personal or Team. |
| Workspace | A credit container that groups related projects. |
| Project | Sets the allowed model list and holds a credit balance. |
| API key | A scoped credential that inherits its project's models and credits. |
On first sign-in, onboarding asks for an organization name (an optional lowercase slug is auto-generated). A Team organization can invite members and assign roles; a Personal organization is single-user and hides the Team page.
Workspaces & projects
The Resources page is where you create workspaces and projects and move credits between them.
Create a workspace
A workspace groups projects and holds a pool of credits you can allocate downward. Most teams use one workspace per product or environment.
Create a project and pick allowed models
Inside a workspace, create a project and choose its allowed models (at least one). Those models — and only those — can be called by every key in the project. Model access is set per project, never per key.
Allocate credits
Credits flow down the tree: organization → workspace → project. Allocate a budget to the project so its keys have something to spend. See How credits work for the terminology.
Managing API keys
The Create key wizard walks you through naming the key, choosing (or creating) the workspace and project, and optionally seeding starting credits. A key is always scoped to one project and inherits that project's models and credit balance.
Keys start with atp- and are 92 characters long. The full secret is displayed once at creation — copy and store it safely. Afterwards only the prefix is visible.
The API keys page lists every key in the organization across all workspaces and projects. Search by name or prefix, filter by workspace, and revoke a key at any time — revoked keys stop working immediately but stay in the roster for audit.
Team & roles
Team organizations can add people and assign them roles at the workspace or project level. There are three roles:
| Role | Can do |
|---|---|
| Owner | Full control. Read-only in the UI and cannot be removed. |
| Admin | Manage members, resources, keys, and credit allocation. |
| Member | Read and use what they have access to. |
Invite by email sends an invitation the person must accept; add existing user attaches an account that already exists immediately. Personal organizations have no team and show a note instead of the members panel.
Usage & logs
Three places tell you what happened and what it cost:
Credit summary (Available, Received, Consumed), top models by month, and per-key token totals.
A per-request audit trail. Filter by time range, scope, model, status, or request ID; columns show status and input / output tokens.
Security and admin events — sign-ins, invites, quota changes, and resource updates.
Timestamps render in your local timezone. Token counts are raw totals, not per-million.
How credits work
Usage is paid for in credits. 1 credit = USD 0.01 (100 credits = USD 1). Pay-as-you-go credits don't expire (committed contract credits may carry an expiry date — see Top up & wallet). Balances are shown to four decimal places; a non-zero amount below that shows as <0.0001.
Credits flow down the hierarchy — organization → workspace → project — and a key spends from its project's balance. Each level reports the same four figures:
| Term | Meaning |
|---|---|
| Available | Credits you can spend or allocate at this level. |
| Received | Total credits received from the parent level. |
| Allocated | Credits pushed down to child workspaces or projects. |
| Consumed | Credits actually spent on API calls (project level). |
If a project spends more than it was allocated it is flagged In debt until topped up.
Pricing
Billing is usage-based, measured on input + output tokens. Each model has its own rate; the exact per-model economics are resolved by the gateway's pricing tables at request time, not by a static table on this page. Which models a key may call is scoped by its project.
There is no monthly fee. See the pricing page for the current model lineup and access tiers.
Top up & wallet
Your pay-as-you-go (PAYG) wallet is funded through Stripe from the Billing page. The minimum top-up is USD 5, and every tier converts at a flat 100 credits per dollar:
| Package | Price | Credits |
|---|---|---|
| Starter | USD 5 | 500 |
| Standard | USD 50 | 5,000 |
| Pro | USD 200 | 20,000 |
| Scale | USD 1,000 | 100,000 |
Every top-up is recorded in the Billing history (date, amount, credits added, balance after, status). Top-ups and credits are non-refundable. Credits are shown as a single Wallet balance. Enterprise accounts may also hold committed contract credits (with an expiry date); these are included in that balance and listed on the Billing page.
Top-ups credit your personal account. In a personal organization that balance is what your keys spend directly. For a team organization, allocate credits to it (Console → Resources) — keys in a team org spend from the org's allocated credits, not from an individual's wallet.
Tracking spend
The Usage page summarizes credits and tokens for a chosen period, broken down by model and by API key. The credit allocation tree lets you drill from organization down to workspace and project to see Allocated versus Consumed at each level, so you can tell exactly where credits are going before a balance runs low.
/v1/chat/completionsUse this endpoint with any OpenAI SDK client. The request body follows OpenAI's chat completions schema unchanged. The model field must be a value returned by GET /v1/models.
| Field | Type | Description |
|---|---|---|
| model | string · required | A model id from GET /v1/models. |
| messages | array · required | Chat messages, each { role, content }. |
| max_tokens | integer | Max output tokens. Set high enough for reasoning models. |
| temperature | number | Sampling temperature, 0–2. |
| stream | boolean | Stream the response as SSE chunks. |
| tools | array | Tool/function definitions (OpenAI tool schema). |
Response
{
"id": "chatcmpl-...",
"object": "chat.completion",
"model": "<model>",
"choices": [
{
"index": 0,
"message": { "role": "assistant", "content": "..." },
"finish_reason": "stop"
}
],
"usage": { "prompt_tokens": 9, "completion_tokens": 12, "total_tokens": 21 }
}/v1/messagesAnthropic Messages requests are accepted and normalized by the Gateway. The top-level system field must be a string. The array form used by Anthropic's cache_control feature is not yet supported — sending an array returns 400 invalid_request_error. The anthropic-version header is accepted and forwarded unchanged.
| Field | Type | Description |
|---|---|---|
| model | string · required | A model id from GET /v1/models. |
| max_tokens | integer · required | Max output tokens (Anthropic requires this). |
| messages | array · required | Conversation turns, each { role, content }. |
| system | string | System prompt. Must be a string, not an array. |
| temperature | number | Sampling temperature, 0–1. |
| stream | boolean | Stream as Anthropic SSE events. |
Request
curl https://api.atptoken.ai/v1/messages \
-H "Authorization: Bearer atp-..." \
-H "Content-Type: application/json" \
-d '{
"model": "<model from GET /v1/models>",
"max_tokens": 256,
"messages": [{"role": "user", "content": "hi"}]
}'/v1/models/{model}:generateContentGemini REST-style generateContent and streamGenerateContent routes are supported under both /v1 and /v1beta (the Google GenAI SDK default). Authenticate with x-goog-api-key — the SDK's default.
Request
curl "https://api.atptoken.ai/v1/models/<model>:generateContent" \
-H "x-goog-api-key: atp-..." \
-H "Content-Type: application/json" \
-d '{
"contents": [{ "parts": [{ "text": "hi" }] }]
}'/v1/filesUpload a file to the Gateway and reference it in subsequent chat or messages requests by the returned gw_file_id (format an_<ULID>).
curl https://api.atptoken.ai/v1/files \
-H "Authorization: Bearer atp-..." \
-F "file=@./input.pdf"
# → { "gw_file_id": "an_01H...", ... }multipart/form-data upload with a file part. Max 20 MB. Identical bytes are deduped by SHA-256.
Returns 302 to a short-lived presigned URL on the object store. Follow the redirect to download.
Server-Sent Events
When you set stream: true on /v1/chat/completions, the Gateway streams the upstream provider response as OpenAI-format SSE chunks. The final chunk contains the usage object; the stream ends with data: [DONE].
Event sequence for /v1/messages
/v1/messages with stream: true emits this event sequence regardless of which upstream provider served the request:
event: message_start
data: {"type":"message_start","message":{...}}
event: content_block_start
data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}}
# one per delta:
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"..."}}
event: content_block_stop
data: {"type":"content_block_stop","index":0}
event: message_delta
data: {"type":"message_delta","delta":{"stop_reason":"end_turn"},"usage":{"output_tokens":N}}
event: message_stop
data: {"type":"message_stop"}stop_reason maps from the provider: stop → end_turn, length → max_tokens.
Common responses
All error responses include a request_id — quote it when contacting support. The error envelope follows whichever SDK format you called (OpenAI / Anthropic / Gemini).
200 with empty content
A request can return 200 OK with an empty message — no error, just no text. This is almost always driven by request parameters, not a failure. The most common cause is a max_tokens that is too low for a reasoning model: the budget is spent on internal reasoning before any visible output, so the content comes back empty and finish_reason / stop_reason is length.
Always inspect the response before assuming text exists. Check finish_reason and the usage object: if completion tokens were produced but content is empty, raise max_tokens and re-send. The request is still billed for the tokens it used.