Migrate from Anthropic Claude

Currency note: Epithre prices in IDR (Rupiah). Competitor prices left in USD for comparison reference.

Anthropic's Messages API uses a different wire format than OpenAI. Migration to Epithre means switching to OpenAI-compat shape. Conceptually 1:1 but requires more code changes than coming from OpenAI.

If you'd prefer minimal code change, you can use Anthropic's official client with our base URL, but the response shape won't match what you're parsing. The cleaner path is to switch to the openai SDK.

High-level model mapping

Anthropic model Epithre equivalent Notes
claude-opus-4-1, claude-opus-4-5 epithre-omni (or epithre-prme for very long context) Flagship multimodal.
claude-sonnet-4-5 epithre-omni Same general tier.
claude-haiku-4-5 epithre-lyt Fast, cheap, multimodal.
claude-3-5-sonnet (deprecated) epithre-omni
Voyage embeddings epithre-embed 4000-dim, multimodal.
(Anthropic has no rerank model) epithre-rerank Cohere-compat shape.
(Anthropic has no image model) epithre-iris Image generation + editing.

Wire format differences

Messages

Anthropic shape:

{
  "model": "claude-sonnet-4-5",
  "system": "You are helpful.",
  "messages": [
    {"role": "user", "content": "Halo"},
    {"role": "assistant", "content": [{"type": "text", "text": "..."}]}
  ],
  "max_tokens": 1024
}

Epithre / OpenAI shape:

{
  "model": "epithre-omni",
  "messages": [
    {"role": "system", "content": "You are helpful."},
    {"role": "user", "content": "Halo"},
    {"role": "assistant", "content": "..."}
  ],
  "max_tokens": 1024
}

Key differences:

  1. System prompt is part of messages in OpenAI shape (with role: "system"), not a separate top-level system field.
  2. Assistant content can be a string in OpenAI shape, not always a list of content blocks.
  3. max_tokens is optional in OpenAI shape (model picks a sensible default). It's required in Anthropic.
  4. Stop reasons are called finish_reason (OpenAI) vs stop_reason (Anthropic), with different vocab ("stop" vs "end_turn", "length" vs "max_tokens").

Response shape

Anthropic:

{
  "id": "msg_01...",
  "type": "message",
  "role": "assistant",
  "content": [{"type": "text", "text": "..."}],
  "model": "claude-sonnet-4-5",
  "stop_reason": "end_turn",
  "usage": {"input_tokens": 12, "output_tokens": 8}
}

Epithre:

{
  "id": "chatcmpl-...",
  "object": "chat.completion",
  "model": "epithre-omni",
  "choices": [{
    "index": 0,
    "message": {"role": "assistant", "content": "..."},
    "finish_reason": "stop"
  }],
  "usage": {"prompt_tokens": 12, "completion_tokens": 8, "total_tokens": 20}
}

To get the text reply: response.choices[0].message.content (Epithre) vs response.content[0].text (Anthropic).

Tool use

Anthropic's tool use is {"type": "tool_use", ...} and {"type": "tool_result", ...} content blocks. Epithre uses OpenAI's tool_calls field on the assistant message and role: "tool" messages for results. Shape differs but capability is the same.

Anthropic:

{"role": "assistant", "content": [
  {"type": "tool_use", "id": "toolu_01", "name": "get_weather", "input": {"city": "Jakarta"}}
]}
{"role": "user", "content": [
  {"type": "tool_result", "tool_use_id": "toolu_01", "content": "30C, sunny"}
]}

Epithre:

{"role": "assistant", "content": null, "tool_calls": [
  {"id": "call_01", "type": "function",
   "function": {"name": "get_weather", "arguments": "{\"city\":\"Jakarta\"}"}}
]}
{"role": "tool", "tool_call_id": "call_01", "content": "30C, sunny"}

See the tool use guide for full patterns.

Vision

Anthropic accepts image content blocks:

{"role": "user", "content": [
  {"type": "image", "source": {"type": "base64", "media_type": "image/jpeg", "data": "..."}},
  {"type": "text", "text": "What's in this?"}
]}

Epithre uses OpenAI's image_url content block:

{"role": "user", "content": [
  {"type": "image_url", "image_url": {"url": "data:image/jpeg;base64,..."}},
  {"type": "text", "text": "What's in this?"}
]}

Same model behavior, different field naming.

Prompt caching

This is one of the cleaner migrations: Anthropic and Epithre use identical cache_control markers.

Both:

{"role": "system", "content": [
  {"type": "text",
   "text": "<long stable system prompt>",
   "cache_control": {"type": "ephemeral"}}
]}

Same pricing model too (1.25x write, 0.1x read on input rate). Migration is zero-effort for prompt cache logic.

Extended thinking

Anthropic exposes thinking as a parameter:

{"thinking": {"type": "enabled", "budget_tokens": 16000}}

Epithre uses chat_template_kwargs:

{"chat_template_kwargs": {"enable_thinking": true}}

Both surface the chain in the response (Anthropic as a thinking content block, Epithre prefixed in the message content depending on model). Functional equivalent.

Streaming

Anthropic streams as a sequence of event types: message_start, content_block_start, content_block_delta, content_block_stop, message_stop. Epithre streams as OpenAI's simple data: {choices: [{delta: {...}}]} chunks.

Migration: switch from event-type dispatch to delta accumulation. The OpenAI SDK handles this for you.

# Anthropic stream handler
with anthropic_client.messages.stream(model=..., messages=...) as stream:
    for text in stream.text_stream:
        print(text, end="")

# Epithre stream handler (using openai SDK)
stream = client.chat.completions.create(
    model="epithre-omni",
    messages=...,
    stream=True,
)
for chunk in stream:
    if chunk.choices[0].delta.content:
        print(chunk.choices[0].delta.content, end="")

Pricing comparison

Approximate, 2026-05:

Workload Anthropic typical Epithre
Sonnet input $3 / 1M tok epithre-omni Rp7,000 / 1M tok
Sonnet output $15 / 1M tok epithre-omni Rp25,000 / 1M tok
Haiku input $0.25 / 1M tok epithre-lyt Rp1,000 / 1M tok
Voyage embed $0.12 / 1M tok epithre-embed Rp1,500 / 1M tok
Cache write 1.25x input Same 1.25x
Cache read 0.1x input Same 0.1x
Batch 50% off Same 50% off

Roughly 80-90% cheaper on chat, equivalent ratio model on prompt cache and batch.

What Anthropic has that Epithre doesn't (yet)

What Epithre has that Anthropic doesn't

Working example: migrating a chat agent

# BEFORE (Anthropic)
import anthropic
client = anthropic.Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])

def chat(history, user_msg):
    resp = client.messages.create(
        model="claude-sonnet-4-5",
        system="You are a helpful Indonesian-speaking assistant.",
        messages=history + [{"role": "user", "content": user_msg}],
        max_tokens=1024,
    )
    return resp.content[0].text

# AFTER (Epithre)
from openai import OpenAI
client = OpenAI(
    api_key=os.environ["EPITHRE_KEY"],
    base_url="https://api.epithre.com/v1",
)

def chat(history, user_msg):
    resp = client.chat.completions.create(
        model="epithre-omni",
        messages=[
            {"role": "system", "content": "You are a helpful Indonesian-speaking assistant."},
            *history,
            {"role": "user", "content": user_msg},
        ],
        max_tokens=1024,
    )
    return resp.choices[0].message.content

Differences: switch SDK, move system into messages, parse choices[0].message.content instead of content[0].text.

Migration checklist

Email hello@epithre.com with subject "Anthropic migration" if you hit unexpected differences. We track common gotchas and update this guide as we see them.