Developer reference
CircuitNotion API Documentation
Public reference for the CircuitNotion OpenAI-compatible AI API: base URL, authentication, chat completions, streaming, images, embeddings, completions, models, structured JSON output, function calling, Anthropic Messages compatibility, and error codes.
- Base URL
https://api.circuitnotion.com/v1- Auth
Authorization: Bearer cn_…- Protocols
- OpenAI & Anthropic Messages
Getting started
Introduction
What the CircuitNotion API is and which protocols it speaks.
CircuitNotion is an OpenAI-compatible AI gateway. If your code already talks to the OpenAI API, you can point it at https://api.circuitnotion.com/v1, swap in a CircuitNotion API key, and keep the rest of your integration unchanged. The gateway also speaks the Anthropic Messages protocol, so terminal agents such as Claude Code work against the same account and the same wallet.
- OpenAI Chat Completions, Responses, Images, Embeddings and Models endpoints under https://api.circuitnotion.com/v1
- Anthropic Messages and token counting under https://api.circuitnotion.com
- Server-sent-event streaming on every generation endpoint
- Function/tool calling and structured JSON output
- Prepaid wallet billing in RWF, with per-key rate and spend limits
Getting started
Quickstart
Send your first request in under five minutes.
- Create an account and fund your wallet at https://payment.circuitnotion.com.
- Generate an API key in the developer platform at https://platform.circuitnotion.com. The key is shown once and starts with cn_.
- Point any OpenAI-compatible client at https://api.circuitnotion.com/v1.
curl https://api.circuitnotion.com/v1/chat/completions \
-H "Authorization: Bearer $CIRCUITNOTION_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "circuit-2-turbo",
"messages": [{"role": "user", "content": "Hello!"}]
}'import os
from openai import OpenAI
client = OpenAI(
base_url="https://api.circuitnotion.com/v1",
api_key=os.environ["CIRCUITNOTION_API_KEY"],
)
response = client.chat.completions.create(
model="circuit-2-turbo",
messages=[{"role": "user", "content": "Hello!"}],
)
print(response.choices[0].message.content)import OpenAI from 'openai';
const client = new OpenAI({
baseURL: 'https://api.circuitnotion.com/v1',
apiKey: process.env.CIRCUITNOTION_API_KEY,
});
const response = await client.chat.completions.create({
model: 'circuit-2-turbo',
messages: [{ role: 'user', content: 'Hello!' }],
});
console.log(response.choices[0].message.content);Getting started
Authentication
Bearer tokens, key format, and how to keep keys safe.
Every request carries a CircuitNotion API key as a bearer token. Keys are prefixed with cn_ and are displayed exactly once at creation time, so store the value immediately in a secret manager. The Anthropic-compatible endpoints additionally accept the x-api-key header used by Anthropic clients.
Authorization: Bearer cn_YOUR_KEY
Content-Type: application/json
# Anthropic-style clients may instead send:
x-api-key: cn_YOUR_KEY- Keep keys server-side. A key in browser or mobile client code is a published key.
- Use one key per environment or per service so a leak can be revoked in isolation.
- Revoke an exposed key from the developer platform; revocation takes effect on the next request.
- Each key carries an independent requests-per-minute limit and an optional daily RWF spend cap.
API reference
Chat completions
POST /v1/chat/completions — the primary text generation endpoint.
The Chat Completions endpoint accepts text and multimodal message content, developer messages, function tools, parallel tool calls, JSON response formats, the modern max_completion_tokens parameter, and the legacy max_tokens alias.
| Field | Type | Description |
|---|---|---|
model | string | A model ID from GET /v1/models, for example circuit-2-turbo. |
messages | array | Conversation so far. Roles: system, developer, user, assistant, tool. |
stream | boolean | Stream the response as server-sent events. Defaults to false. |
max_completion_tokens | integer | Upper bound on generated tokens. max_tokens is accepted as a legacy alias. |
temperature | number | Sampling temperature. Lower values are more deterministic. |
tools | array | Function tool definitions the model may call. |
response_format | object | Set to a JSON schema or json_object to constrain output shape. |
curl https://api.circuitnotion.com/v1/chat/completions \
-H "Authorization: Bearer $CIRCUITNOTION_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "circuit-2-turbo",
"messages": [
{"role": "system", "content": "You are a concise assistant."},
{"role": "user", "content": "Summarise the CAP theorem in two sentences."}
],
"max_completion_tokens": 200
}'{
"id": "chatcmpl-...",
"object": "chat.completion",
"created": 1758499200,
"model": "circuit-2-turbo",
"choices": [
{
"index": 0,
"message": { "role": "assistant", "content": "..." },
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 28,
"completion_tokens": 64,
"total_tokens": 92
}
}API reference
Streaming
Server-sent events, delta chunks, and the final usage chunk.
Set stream: true to receive the response as server-sent events. Each chunk carries an incremental delta. The stream ends with a usage chunk that reports billing totals and carries no choices, so client code must tolerate an empty choices array before the [DONE] sentinel.
stream = client.chat.completions.create(
model="circuit-2-turbo",
messages=[{"role": "user", "content": "Explain streaming in one paragraph."}],
stream=True,
)
for chunk in stream:
# The final billing-usage chunk has no choices.
if chunk.choices and chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="", flush=True)const stream = await client.chat.completions.create({
model: 'circuit-2-turbo',
messages: [{ role: 'user', content: 'Explain streaming in one paragraph.' }],
stream: true,
});
for await (const chunk of stream) {
const delta = chunk.choices?.[0]?.delta?.content;
if (delta) process.stdout.write(delta);
}API reference
Image generation
POST /v1/images/generations — text-to-image.
Image generation follows the OpenAI Images API. Images are billed per image rather than per token, at a rate that depends on the model and the requested size.
curl https://api.circuitnotion.com/v1/images/generations \
-H "Authorization: Bearer $CIRCUITNOTION_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-image-1",
"prompt": "A topographic map of an imaginary island, muted print colours",
"size": "1024x1024",
"n": 1
}'image = client.images.generate(
model="gpt-image-1",
prompt="A topographic map of an imaginary island, muted print colours",
size="1024x1024",
n=1,
)
print(image.data[0].url or image.data[0].b64_json[:64])API reference
Embeddings
POST /v1/embeddings — vector representations for search and RAG.
The embeddings endpoint accepts a string or an array of strings and returns float vectors. text-embedding-3-small, text-embedding-3-large, and text-embedding-ada-002 are supported, billed against the same wallet as generation requests.
result = client.embeddings.create(
model="text-embedding-3-small",
input=["postgres connection pooling", "database connection limits"],
)
for item in result.data:
print(item.index, len(item.embedding))curl https://api.circuitnotion.com/v1/embeddings \
-H "Authorization: Bearer $CIRCUITNOTION_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "text-embedding-3-small",
"input": "postgres connection pooling"
}'API reference
Responses and completions
POST /v1/responses — the stateless Responses API adapter.
The Responses adapter accepts text and message input, function calls, function-call outputs, image input conversion, and Responses-style SSE events. It is intentionally stateless: send prior messages and tool outputs in input on every call. previous_response_id is rejected rather than silently ignored, so conversation state stays where your application can see it.
response = client.responses.create(
model="circuit-2-turbo",
input=[{"role": "user", "content": "Give me three names for a caching library."}],
)
print(response.output_text)API reference
Models
GET /v1/models — the live catalogue.
Query the catalogue at runtime rather than hardcoding model availability. Individual model metadata is available at GET /v1/models/{model_id}. The catalogue is the authoritative list of which IDs your key can call today.
curl https://api.circuitnotion.com/v1/models \
-H "Authorization: Bearer $CIRCUITNOTION_API_KEY"{
"object": "list",
"data": [
{ "id": "circuit-2-turbo", "object": "model", "owned_by": "circuitnotion" },
{ "id": "circuit-3", "object": "model", "owned_by": "circuitnotion" },
{ "id": "kimi-k3", "object": "model", "owned_by": "moonshot" }
]
}Guides
Structured JSON output
Constrain responses to a schema your parser can trust.
Pass response_format to make the model emit JSON instead of prose. A json_schema format with strict: true is the safest option because the shape is enforced rather than merely requested.
response = client.chat.completions.create(
model="circuit-2-turbo",
messages=[{"role": "user", "content": "Extract the invoice fields from: INV-4192, 82,500 RWF, due 2026-10-01"}],
response_format={
"type": "json_schema",
"json_schema": {
"name": "invoice",
"strict": True,
"schema": {
"type": "object",
"properties": {
"number": {"type": "string"},
"amount_rwf": {"type": "number"},
"due_date": {"type": "string"},
},
"required": ["number", "amount_rwf", "due_date"],
"additionalProperties": False,
},
},
},
)
import json
print(json.loads(response.choices[0].message.content))Guides
Function calling
Let the model call your own functions and read the results.
Declare tools on the request. When the model decides to call one, the response contains tool_calls instead of content. Execute the call yourself, then send the result back as a message with role tool and the matching tool_call_id. Parallel tool calls are supported.
tools = [{
"type": "function",
"function": {
"name": "get_wallet_balance",
"description": "Return the current wallet balance in RWF.",
"parameters": {
"type": "object",
"properties": {"account_id": {"type": "string"}},
"required": ["account_id"],
},
},
}]
messages = [{"role": "user", "content": "What is the balance for account a-91?"}]
first = client.chat.completions.create(
model="circuit-2-turbo", messages=messages, tools=tools,
)
call = first.choices[0].message.tool_calls[0]
messages.append(first.choices[0].message)
messages.append({
"role": "tool",
"tool_call_id": call.id,
"content": '{"balance_rwf": 12500}',
})
final = client.chat.completions.create(
model="circuit-2-turbo", messages=messages, tools=tools,
)
print(final.choices[0].message.content)Guides
Coding agents and Anthropic compatibility
Claude Code, Cline, Roo Code, Hermes, and other terminal agents.
Agents that speak the OpenAI protocol — Cline, Roo Code, Hermes and similar — use the standard base URL https://api.circuitnotion.com/v1 with a cn_ key, a model ID from the catalogue, and streaming plus tool calling enabled.
Claude Code
Claude Code speaks the Anthropic Messages protocol, so it points at the root host https://api.circuitnotion.com with no /v1 suffix. POST /v1/messages and POST /v1/messages/count_tokens are both supported; token counting is free.
mkdir -p ~/.claude
cat > ~/.claude/settings.json <<'EOF'
{
"env": {
"ANTHROPIC_BASE_URL": "https://api.circuitnotion.com",
"ANTHROPIC_AUTH_TOKEN": "cn_YOUR_KEY",
"ANTHROPIC_MODEL": "kimi-k3",
"ANTHROPIC_SMALL_FAST_MODEL": "kimi-k3"
}
}
EOF
claudeprovider: OpenAI Compatible
base_url: https://api.circuitnotion.com/v1
api_key: cn_YOUR_KEY
model: circuit-2-turbo
streaming: true
tool_calling: trueOperations
Billing and limits
Prepaid wallet, reservations, idempotency, and rate limits.
Billing is prepaid in RWF. Every generation request reserves its estimated maximum cost before the upstream provider is called, then settles against actual token usage once the response completes. A request that would exceed the available balance is rejected with 402 rather than partially served.
- Send an Idempotency-Key header on retryable generation requests. Reusing an active or completed key returns 409 and never creates a second charge.
- Reservations use a softer output cap than the max_tokens agents typically advertise, so a single agent turn does not lock the whole balance.
- Each API key has an independent requests-per-minute limit and an optional daily RWF spending cap.
- Balance, transactions, and top-ups live on the billing dashboard at https://payment.circuitnotion.com.
Operations
Errors
Status codes, the error envelope, and retry policy.
Errors use the OpenAI error envelope, so existing OpenAI SDK error handling works unchanged.
{
"error": {
"message": "Insufficient credits to complete this request.",
"type": "invalid_request_error",
"param": null,
"code": null
}
}| Status | Meaning | Retry |
|---|---|---|
400 | Invalid request | No — fix the request |
401 | Missing, invalid, or revoked API key | No |
402 | Insufficient credits | No — top up the wallet |
404 | Unknown model or endpoint | No |
409 | Idempotency-Key reused while active or completed | No — read the original result |
429 | Per-key rate limit reached | Yes — exponential backoff |
500 | Internal processing error | Yes — exponential backoff |
503 | Upstream provider unavailable or not configured | Yes — exponential backoff |
Operations
Production checklist
What to verify before you put the integration in front of users.
- Store the API key in a server-side secret manager, never in client code or version control.
- Set explicit connection and request timeouts.
- Retry only transient errors, with exponential backoff and a bounded attempt count.
- Handle the empty-choices streaming usage chunk.
- Set max_completion_tokens to bound latency and cost.
- Read GET /v1/models at runtime instead of hardcoding model availability.
- Monitor credits and transactions on the billing dashboard.
- Revoke keys immediately if they are exposed.