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

  1. Open Settings → Developer API and create a key (format sk-zenic-…).
  2. Pick a base URL below — it differs between Claude Code and OpenCode.
  3. Send requests to azno-1.5-mini. Other model IDs are rejected on OpenAI routes; Anthropic routes accept claude-* aliases.

URLs

ClientBase URLEndpoint used
Claude Codehttps://api.zenic.my/v1/messages
OpenCode (Anthropic)https://api.zenic.my/v1/messages
OpenAI SDK / curlhttps://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

  1. Create an API key in Settings → Developer API.
  2. Edit ~/.claude/settings.json (or settings.local.json for machine-only overrides).
  3. Paste the env block below and replace the token.
  4. Restart Claude Code or open a new terminal session.
  5. 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/messages itself.
  • ANTHROPIC_AUTH_TOKEN — your sk-zenic-… key.
  • ANTHROPIC_MODEL — set to azno-1.5-mini. Claude Sonnet/Opus IDs also work as aliases; all map to Azno 1.5 Mini.
404 on /v1/v1/messages? You included /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.

  1. Create ~/.config/opencode/opencode.json.
  2. Set ZENIC_API_KEY in your shell profile or pass it inline.
  3. 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.

OpenCode vs Claude Code base URL
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:

EventMeaning
message_startEmpty assistant message shell + input token count
content_block_startNew text or tool_use block begins
content_block_deltaIncremental text (text_delta) or tool JSON (input_json_delta)
content_block_stopBlock finished
message_deltaFinal stop_reason (end_turn or tool_use) + output tokens
message_stopStream 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 key
  • 400 model_not_found — OpenAI route: only azno-1.5-mini accepted
  • 402 insufficient_balance — weekly free quota used and no paid balance
  • 404 — 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