Create a message
POST/messages
Takes a conversation in Anthropic's Messages format and answers in the same format, with an Evanesca model: Claude Code and Anthropic's SDKs work once they have the base URL and an Evanesca key. Underneath, each call is a chat completion — the same models, the same European operators, the same billing per token.
SoonOpening soon: what follows describes the service as it will work.
https://evanesca.app/api/v1/messagesOpens at launchBearer $EVANESCA_API_KEYx-api-keystringrequiredYour key, which starts with sk-eva-. Authorization: Bearer followed by the key works too: that is where Claude Code sends it when ANTHROPIC_AUTH_TOKEN is set.
anthropic-versionstringSent by Anthropic's SDKs; accepted, and has no effect here.
Content-Typestringrequiredapplication/json: the body is JSON.
Request body
application/jsonmodelstringrequiredAn Evanesca chat model ID from the model table, for example gemma-4-26b — never a Claude ID, which answers 404.
messagesarrayrequiredThe turns, with roles user and assistant. Content is a string or a list of blocks: text, image (base64 or URL), tool_use, tool_result and document.
systemstring | arrayThe system prompt; text blocks are joined.
max_tokensintegerCeiling on the answer's tokens, thinking included. Anthropic requires it; here it may be left out.
streambooleantrue sends Anthropic's server-sent events (Streaming).
tools, tool_choicearray · objectAnthropic's format: name, description, input_schema; tool_choice auto, any, none or tool (which models call tools).
temperature, top_p, top_k, stop_sequencesvariousPassed to the operator; stop_sequences becomes OpenAI's stop list.
Where it runs, what it costs
Evanesca · on the record
- Runs in
- Mistral AI · EU region of Mistral AI
mistral-large-4,mistral-small-4-119b - AKI.IO · Germany
deepseek-v4.1-flash,glm-5.3,gemma-4-26b - Scaleway · Paris, France
qwen3.8-27b,deepseek-v4-flash,glm-5.2,qwen3.6-35b,qwen3.5-397b,gpt-oss-120b,qwen3-coder-30b,qwen3-235b,mistral-small-3.2,llama-3.3-70b,pixtral-12b - Infomaniak · Switzerland
apertus-v1.5-70b
- Mistral AI · EU region of Mistral AI
- Operator keeps
Mistral AI keeps API inputs and outputs “for thirty (30) rolling days to monitor abuse” (Mistral's privacy policy).
AKI.IO states that “prompts, inputs, and generated outputs are processed in volatile memory and are not logged, stored, or analyzed by AKI.IO” (AKI.IO); its privacy policy keeps usage data — request ID, model, status, timestamps, token usage — for 90 days, for billing and service maintenance (AKI.IO's privacy policy).
Scaleway may keep and read requests that cause errors or look abusive, for up to 14 days (Scaleway's data privacy page).
OVHcloud states “zero data retention: we keep only the data required for billing purposes” (OVHcloud AI Endpoints).
Infomaniak states that queries are “neither recorded nor used to train models” (Infomaniak AI Tools).
- Costs
- What the same call costs as a chat completion: per token, VAT included, €0.168 to €3.024 per million input tokens and €0.336 to €9.24 per million output tokens, reasoning counted as output. A quick question (300 tokens in, 400 out) on gemma-4-26b: about €0.0004 (every price).
- Response headers
x-evanesca-operator: mistral | aki | scaleway | infomaniakx-evanesca-location: mistral-eu-region | germany | paris | switzerland
How the request is translated
Evanesca turns the Messages request into a chat completion, runs it as POST /chat/completions would, and turns the answer back. The system prompt becomes a system message, tool_use blocks become tool calls, and each tool_result is sent as a tool message placed before the rest of the user turn. Images go to the model as they are; a text document passes as text, while a binary document such as a PDF is replaced by a short note asking for its text — no file is forwarded.
Some fields have nothing to map to and are ignored without an error: thinking, cache_control, metadata, the anthropic-beta header, thinking blocks in the history, and Anthropic's server tools (tools without an input_schema). disable_parallel_tool_use is ignored too, because some operators refuse its translation: a model may then call several tools in one turn.
The answer is a standard message object. Its model field repeats the ID you sent; stop_reason is end_turn, max_tokens, tool_use or refusal. In usage, input_tokens leaves out the tokens the operator read from its cache, which are counted in cache_read_input_tokens; cache_creation_input_tokens is always 0. A model's thinking is not returned as thinking blocks: they would need a signature only Anthropic can give.
Streaming
With "stream": true, the answer arrives as Anthropic's events: message_start, then for each block content_block_start, its deltas (text_delta for text, input_json_delta for a tool call's arguments) and content_block_stop, then message_delta with the stop reason and the usage, and message_stop. While a model is still thinking, ping events keep the connection alive. The usage in message_delta is what the call is billed on.
Claude Code
Claude Code reads its base URL, its key and its models from the environment. Point all four model variables at Evanesca model IDs: until they are set, Claude Code asks for Claude models, and a Claude ID answers 404 model_not_found — Evanesca never swaps in another model you did not choose. Claude Code works through tools, so pick a model that returns tool calls.
export ANTHROPIC_BASE_URL="https://evanesca.app/api"
export ANTHROPIC_AUTH_TOKEN="$EVANESCA_API_KEY"
export ANTHROPIC_MODEL="gemma-4-26b"
export ANTHROPIC_DEFAULT_OPUS_MODEL="gemma-4-26b"
export ANTHROPIC_DEFAULT_SONNET_MODEL="gemma-4-26b"
export ANTHROPIC_DEFAULT_HAIKU_MODEL="gemma-4-26b"
claudeError format
Errors keep Anthropic's envelope — {"type": "error", "error": {"type", "message"}} — so Anthropic's SDKs raise them as usual, but error.type carries the values of Evanesca's API listed below, the same as on POST /chat/completions.
Errors
| Status | type | Meaning |
|---|---|---|
| 400 | invalid_ | The body is not valid JSON, or model or messages is missing. |
| 401 | invalid_ | No key, a malformed key, or a revoked one. |
| 402 | insufficient_ | Your credit is at zero or below. Add credit to continue. |
| 404 | model_ | A Claude ID, an unknown ID, a model Evanesca does not serve, or an embedding model. GET /models?kind=chat lists the valid ones. |
| 502 | upstream_ | The operator could not be reached, sent an unreadable answer, or turned down Evanesca's own account. Your key is not the problem: retry. |
| 503 | model_ | The model is paused, or its operator cannot be reached by Evanesca for now. Retry later or pick another model. |
Other operator errors reach you with its status and body, and Retry-After when it sends one (errors).
curl https://evanesca.app/api/v1/messages \
-H "x-api-key: $EVANESCA_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "Content-Type: application/json" \
-d '{
"model": "gemma-4-26b",
"max_tokens": 1024,
"messages": [{"role": "user", "content": "Hello"}]
}'import os
import anthropic
client = anthropic.Anthropic(
base_url="https://evanesca.app/api",
api_key=os.environ["EVANESCA_API_KEY"],
)
message = client.messages.create(
model="gemma-4-26b",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello"}],
)
print(message.content[0].text)import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic({
baseURL: "https://evanesca.app/api",
apiKey: process.env.EVANESCA_API_KEY,
});
const message = await client.messages.create({
model: "gemma-4-26b",
max_tokens: 1024,
messages: [{ role: "user", content: "Hello" }],
});
console.log(message.content[0].type === "text" ? message.content[0].text : message.content);package main
import (
"fmt"
"io"
"net/http"
"os"
"strings"
)
func main() {
body := strings.NewReader(`{"model":"gemma-4-26b","max_tokens":1024,"messages":[{"role":"user","content":"Hello"}]}`)
req, err := http.NewRequest("POST", "https://evanesca.app/api/v1/messages", body)
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer "+os.Getenv("EVANESCA_API_KEY"))
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer res.Body.Close()
out, _ := io.ReadAll(res.Body)
fmt.Println(res.Status, string(out))
}{
"id": "msg_…",
"type": "message",
"role": "assistant",
"model": "gemma-4-26b",
"content": [{ "type": "text", "text": "…" }],
"stop_reason": "end_turn",
"stop_sequence": null,
"usage": {
"input_tokens": 9,
"output_tokens": 12,
"cache_read_input_tokens": 0,
"cache_creation_input_tokens": 0
}
}{
"type": "error",
"error": {
"type": "model_not_found",
"message": "Evanesca does not serve \"claude-sonnet-4-5\": every model it serves runs in Europe. Set ANTHROPIC_MODEL … to Evanesca model IDs. …"
}
}