Text
Chat Completions, Responses and Anthropic Messages, with streaming, tools, vision and caching.
Endpoints
Use whichever format your code already speaks. Every route also works without the /v1 prefix.
| Method | Path | Format |
|---|---|---|
| POST | /v1/chat/completions | OpenAI Chat Completions: streaming, tools, vision |
| POST | /v1/responses | OpenAI Responses, with streaming |
| POST | /v1/messages | Anthropic Messages: streaming, tools, prompt caching, extended thinking |
| POST | /v1/messages/count_tokens | Anthropic token counting. Free, never billed |
Chat Completions
The OpenAI Chat Completions format. model is any text model ID from the models page; the response echoes the same ID.
curl https://api.greentokens.io/v1/chat/completions \
-H "Authorization: Bearer $GREENTOKENS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-6-sol",
"messages": [
{"role": "system", "content": "You are a concise assistant."},
{"role": "user", "content": "Explain rate limiting in one sentence."}
],
"max_tokens": 200
}'Streaming
Set stream: true to receive tokens as they're generated, as server-sent events. Token usage arrives on the final chunk.
stream = client.chat.completions.create(
model="claude-sonnet-5",
messages=[{"role": "user", "content": "Write a haiku about latency."}],
stream=True,
)
for chunk in stream:
if chunk.choices and chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="", flush=True)Responses
The OpenAI Responses format, with input, instructions and output_text. Streaming works the same way.
response = client.responses.create(
model="gpt-6-sol",
instructions="Answer in one short paragraph.",
input="What is a context window?",
)
print(response.output_text)Anthropic Messages
The Anthropic Messages format. Point the Anthropic SDK at https://api.greentokens.io (no /v1): the SDK adds the path itself. Errors on this route use the Anthropic error format.
from anthropic import Anthropic
client = Anthropic(base_url="https://api.greentokens.io", api_key="sk-gt-…")
message = client.messages.create(
model="claude-opus-5-5",
max_tokens=1024,
system="You are a senior code reviewer.",
messages=[{"role": "user", "content": "Review: def add(a, b): return a - b"}],
)
print(message.content[0].text)Tool calling
Define functions the model can call. The response contains the call; your code runs it and sends the result back in a tool message. Tool use also works through Messages in the Anthropic format.
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Current weather for a city",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
},
}]
response = client.chat.completions.create(
model="gpt-6-sol",
messages=[{"role": "user", "content": "Is it raining in Lisbon?"}],
tools=tools,
)
call = response.choices[0].message.tool_calls[0]
print(call.function.name, call.function.arguments) # get_weather {"city": "Lisbon"}Images as input
Models that accept images take them as image_url parts, including data URLs. Filter by Text + image on the models page to see which ones.
import base64
with open("chart.png", "rb") as f:
image = base64.b64encode(f.read()).decode()
response = client.chat.completions.create(
model="gpt-6-sol",
messages=[{
"role": "user",
"content": [
{"type": "text", "text": "What does this chart show?"},
{"type": "image_url", "image_url": {"url": f"data:image/png;base64,{image}"}},
],
}],
)
print(response.choices[0].message.content)Prompt caching
Repeated input is billed at the lower cached-input price. Put the large, unchanging part of a prompt (system instructions, reference documents, tool definitions) first, and keep it byte-for-byte identical between calls.
- Anthropic Messages: mark the reusable block with
"cache_control": {"type": "ephemeral"}, as below. - OpenAI formats: caching of repeated prefixes is automatic. Check
cached_tokensin the usage.
message = client.messages.create(
model="claude-sonnet-5",
max_tokens=1024,
system=[{
"type": "text",
"text": LONG_REFERENCE_DOCUMENT, # the large, unchanging part
"cache_control": {"type": "ephemeral"},
}],
messages=[{"role": "user", "content": "Summarise section 3."}],
)
print(message.usage.cache_read_input_tokens) # > 0 once the cache is warmCounting tokens
Estimate a request's input size before sending it. /v1/messages/count_tokens is free and never billed.
count = client.messages.count_tokens(
model="claude-sonnet-5",
messages=[{"role": "user", "content": "How many tokens is this?"}],
)
print(count.input_tokens)