Developer
Zenic API
OpenAI- and Anthropic-compatible REST API. One model — azno-1.5-mini. Works with Claude Code, OpenCode, curl, and standard SDKs. Create keys in Settings → Developer API.
Quick start
- Open Settings → Developer API and create a key (format sk-zenic-…).
- Pick a base URL below — it differs between Claude Code and OpenCode.
- Send requests to azno-1.5-mini. Other model IDs are rejected on OpenAI routes; Anthropic routes accept claude-* aliases.
URLs
| Client | Base URL | Endpoint used |
|---|---|---|
| Claude Code | https://api.zenic.my | /v1/messages |
| OpenCode (Anthropic) | https://api.zenic.my/v1 | /messages |
| OpenAI SDK / curl | https://api.zenic.my/v1 | /chat/completions |
Authentication
OpenAI clients: Authorization: Bearer sk-zenic-…
Anthropic clients: x-api-key: sk-zenic-… or the same Bearer header.
Claude Code
Claude Code talks to the Anthropic Messages API. Point it at Zenic with three env vars. Streaming and tool calls are enabled by default — no extra flags.
Setup
- Create an API key in Settings → Developer API.
- Edit ~/.claude/settings.json (or settings.local.json for machine-only overrides).
- Paste the env block below and replace the token.
- Restart Claude Code or open a new terminal session.
- Run claude in your project directory.
{
"env": {
"ANTHROPIC_BASE_URL": "https://api.zenic.my",
"ANTHROPIC_AUTH_TOKEN": "sk-zenic-...",
"ANTHROPIC_MODEL": "azno-1.5-mini"
}
}Env vars
ANTHROPIC_BASE_URL— API host without/v1. Claude Code appends/v1/messagesitself.ANTHROPIC_AUTH_TOKEN— yoursk-zenic-…key.ANTHROPIC_MODEL— set toazno-1.5-mini. Claude Sonnet/Opus IDs also work as aliases; all map to Azno 1.5 Mini.
/v1 in ANTHROPIC_BASE_URL. Use https://api.zenic.my instead.Verify
# Should return authentication_error (401), not 404
curl -s -X POST https://api.zenic.my/v1/messages \
-H "x-api-key: sk-zenic-test" \
-H "Content-Type: application/json" \
-H "anthropic-version: 2023-06-01" \
-d '{"model":"azno-1.5-mini","max_tokens":16,"messages":[{"role":"user","content":"ping"}]}'A 404 means the base URL is wrong. A 401 with authentication_error means the route is reachable — swap in your real key.
OpenCode
OpenCode supports two integration paths. For agent mode with tools, use the Anthropic provider. For a simpler chat-style setup, use an OpenAI-compatible custom provider.
Option A — Anthropic provider (recommended)
Uses POST /v1/messages with native tool streaming. OpenCode's Anthropic SDK expects baseURL to include /v1 — the opposite of Claude Code.
- Create ~/.config/opencode/opencode.json.
- Set ZENIC_API_KEY in your shell profile or pass it inline.
- Paste the config below and pick the model on startup.
{
"$schema": "https://opencode.ai/config.json",
"model": "anthropic/azno-1.5-mini",
"provider": {
"anthropic": {
"options": {
"baseURL": "https://api.zenic.my/v1",
"apiKey": "{env:ZENIC_API_KEY}"
},
"models": {
"azno-1.5-mini": {
"name": "Azno 1.5 Mini"
}
}
}
}
}# ~/.bashrc or ~/.zshrc
export ZENIC_API_KEY="sk-zenic-..."Option B — OpenAI-compatible provider
Uses POST /v1/chat/completions. Good when you want the OpenAI SDK path inside OpenCode or only need text completions.
{
"$schema": "https://opencode.ai/config.json",
"model": "zenic/azno-1.5-mini",
"provider": {
"zenic": {
"npm": "@ai-sdk/openai-compatible",
"name": "Zenic",
"options": {
"baseURL": "https://api.zenic.my/v1",
"apiKey": "{env:ZENIC_API_KEY}"
},
"models": {
"azno-1.5-mini": {
"name": "Azno 1.5 Mini",
"limit": {
"context": 256000,
"output": 8192
}
}
}
}
}
}Select model in OpenCode
After editing the config, run opencode and choose anthropic/azno-1.5-mini or zenic/azno-1.5-mini depending on which option you configured. Streaming is on by default in the TUI.
Claude Code:
https://api.zenic.my (no /v1).OpenCode Anthropic:
https://api.zenic.my/v1 (with /v1).Swapping them produces 404 errors.
Streaming
Both APIs stream over Server-Sent Events (SSE). Pass -N to curl so chunks print immediately. Claude Code and OpenCode always stream — you don't configure this separately.
OpenAI — POST /chat/completions
Set "stream": true. Response is text/event-stream with data: {…} lines.
curl -N https://api.zenic.my/v1/chat/completions \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "azno-1.5-mini",
"stream": true,
"messages": [
{ "role": "user", "content": "Write a haiku about debugging." }
]
}'Typical chunk sequence:
data: {"id":"chatcmpl-…","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"Hello"},"finish_reason":null}]}
data: {"id":"chatcmpl-…","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":" world"},"finish_reason":null}]}
data: {"id":"chatcmpl-…","object":"chat.completion.chunk","choices":[],"usage":{"prompt_tokens":12,"completion_tokens":2,"total_tokens":14}}
data: [DONE]Read choices[0].delta.content from each chunk. Stop when you see data: [DONE].
Anthropic — POST /messages
Set "stream": true and header anthropic-version: 2023-06-01. Events use event: + data: pairs.
curl -N https://api.zenic.my/v1/messages \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-H "anthropic-version: 2023-06-01" \
-d '{
"model": "azno-1.5-mini",
"max_tokens": 1024,
"stream": true,
"messages": [
{ "role": "user", "content": "Write a haiku about debugging." }
]
}'Event lifecycle:
| Event | Meaning |
|---|---|
| message_start | Empty assistant message shell + input token count |
| content_block_start | New text or tool_use block begins |
| content_block_delta | Incremental text (text_delta) or tool JSON (input_json_delta) |
| content_block_stop | Block finished |
| message_delta | Final stop_reason (end_turn or tool_use) + output tokens |
| message_stop | Stream complete |
Example text delta:
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Hello"}}
event: message_delta
data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"output_tokens":5}}
event: message_stop
data: {"type":"message_stop"}For tool-using agents (Claude Code, OpenCode), watch for stop_reason: "tool_use" and content_block entries with type: "tool_use".
OpenAI-compatible API
List models
GET https://api.zenic.my/v1/models
{
"object": "list",
"data": [{ "id": "azno-1.5-mini", "object": "model", "owned_by": "zenic" }]
}Chat completions
POST /chat/completions — standard OpenAI shape. Context capped at 256k tokens (oldest turns dropped). Roles: user, assistant.
POST https://api.zenic.my/v1/chat/completions
Authorization: Bearer sk-zenic-...
Content-Type: application/json
{
"model": "azno-1.5-mini",
"stream": false,
"messages": [
{ "role": "user", "content": "Your prompt" }
]
}Anthropic-compatible API
POST /messages — Anthropic Messages API shape. Required field: max_tokens. Tool definitions in tools are forwarded; responses may include tool_use blocks.
POST https://api.zenic.my/v1/messages
x-api-key: sk-zenic-...
anthropic-version: 2023-06-01
Content-Type: application/json
{
"model": "azno-1.5-mini",
"max_tokens": 4096,
"stream": false,
"system": "Optional system prompt (merged with Zenic safety policy).",
"messages": [
{ "role": "user", "content": "Hello" }
]
}Non-streaming response shape:
{
"id": "msg_…",
"type": "message",
"role": "assistant",
"model": "azno-1.5-mini",
"content": [{ "type": "text", "text": "Hello!" }],
"stop_reason": "end_turn",
"usage": { "input_tokens": 12, "output_tokens": 4 }
}SDK examples
cURL (non-streaming)
curl https://api.zenic.my/v1/chat/completions \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "azno-1.5-mini",
"messages": [
{ "role": "user", "content": "Explain recursion in one paragraph." }
]
}'Python — OpenAI SDK
from openai import OpenAI
client = OpenAI(
api_key="YOUR_API_KEY",
base_url="https://api.zenic.my/v1",
)
# Non-streaming
response = client.chat.completions.create(
model="azno-1.5-mini",
messages=[{"role": "user", "content": "Hello"}],
)
print(response.choices[0].message.content)
# Streaming
stream = client.chat.completions.create(
model="azno-1.5-mini",
stream=True,
messages=[{"role": "user", "content": "Hello"}],
)
for chunk in stream:
delta = chunk.choices[0].delta.content
if delta:
print(delta, end="", flush=True)Python — Anthropic SDK
import anthropic
client = anthropic.Anthropic(
api_key="YOUR_API_KEY",
base_url="https://api.zenic.my",
)
# Non-streaming
message = client.messages.create(
model="azno-1.5-mini",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello"}],
)
print(message.content[0].text)
# Streaming
with client.messages.stream(
model="azno-1.5-mini",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello"}],
) as stream:
for text in stream.text_stream:
print(text, end="", flush=True)Errors & limits
HTTP status codes
401— missing or invalid API key400 model_not_found— OpenAI route: only azno-1.5-mini accepted402 insufficient_balance— weekly free quota used and no paid balance404— wrong base URL (see Quick start table)
Anthropic error shape
{
"type": "error",
"error": {
"type": "authentication_error",
"message": "Invalid API key provided."
}
}Limits
- One public model: azno-1.5-mini
- 256k context window — oldest messages trimmed first
- Usage billed against your plan quota, then paid balance