Create a chat completion
POST/chat/completions
Takes a conversation and returns the model's answer, in OpenAI's chat-completions format: the official SDKs work as they are. Streaming, tool calling and reasoning models are below.
SoonOpening soon: what follows describes the service as it will work.
https://evanesca.app/api/v1/chat/completionsOpens at launchBearer $EVANESCA_API_KEYAuthorizationstringrequiredBearer followed by your key, which starts with sk-eva-.
Content-Typestringrequiredapplication/json: the body is JSON.
Request body
application/jsonmodelstringrequiredAn ID from the model table, for example gemma-4-26b.
messagesarrayrequiredThe conversation, as OpenAI-format {role, content} objects: system, user, assistant — and tool for a tool's result.
streambooleantrue sends the answer as server-sent events (Streaming).
max_tokensintegerCeiling on the answer's tokens. On a reasoning model the thinking counts too: leave it out or allow about 1,000 tokens or more (why).
tools, tool_choicearray · string or objectFunction calling, in OpenAI's format (which models).
temperature, top_p, stop…variousOpenAI's sampling parameters, passed to the operator as they are.
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
- Per token, at the model's price, VAT included: €0.168 to €3.024 per million input tokens, €0.336 to €9.24 per million output tokens — reasoning counts 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
What reaches the operator
Evanesca passes the request body to the operator serving the model, so OpenAI's parameters behave as OpenAI documents them, and a parameter an operator does not support is ignored by that operator. Two exceptions keep routing and billing in Evanesca's hands: fields that would ask a router to pick another model or host — models, provider, plugins — are dropped, and a max_tokens above the operator's ceiling for that model is lowered to the ceiling instead of failing.
The answer is a standard chat.completion object. Its model field carries the name the operator gives the model that answered; its usage block is what your credit pays for, to the token.
Streaming
With "stream": true, the answer arrives as server-sent events: data: lines carrying OpenAI-format chunks, ending with data: [DONE]. It is OpenAI's wire format, so the SDKs' streaming helpers work as they are.
Evanesca always asks the operator for usage in the stream (stream_options.include_usage): the last chunk before [DONE] carries the usage object and an empty choices array. That chunk is how a streamed call is charged to the token — expect it if you parse the stream yourself.
Tool calling
Send tools and tool_choice in OpenAI's format: Evanesca passes them to the operator untouched, and tool calls come back in message.tool_calls (or as deltas when you stream). Whether a model emits them depends on the model and its operator, so the catalogue records it per model, from a real call rather than a model card.
Every model in the table returns tool calls.
In GET /models, tool_calling is true or false where it was measured and absent where it was not: filter on it before you deploy an agent.
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Current weather in a city",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
},
}]
answer = client.chat.completions.create(
model="gemma-4-26b",
messages=[{"role": "user", "content": "What's the weather in Lyon?"}],
tools=tools,
)
print(answer.choices[0].message.tool_calls)Reasoning models
These models think before they answer: deepseek-v4.1-flash, glm-5.3, qwen3.8-27b, deepseek-v4-flash, glm-5.2, qwen3.6-35b, qwen3.5-397b, gpt-oss-120b. That thinking is made of completion tokens: it is billed as output, at the model's output price, before any visible text appears. On any model, reasoning tokens the operator reports count as output.
Give them room. With a small max_tokens the whole budget can go into thinking, and the answer comes back with no content and finish_reason "length": it looks empty, but nothing failed. Leave max_tokens out, or allow about 1,000 tokens or more.
Thinking takes time too: on a long prompt, a model such as deepseek-v4.1-flash can work for several minutes before the answer is complete. Raise your HTTP client's timeout (n8n's OpenAI Chat Model node, for one, cuts off at 60 seconds by default) or stream the answer.
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_ | Unknown ID, a model Evanesca does not serve, or an embedding model sent here. GET /models 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. |
Other operator errors reach you with its status and body, and Retry-After when it sends one (errors).
curl https://evanesca.app/api/v1/chat/completions \
-H "Authorization: Bearer $EVANESCA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "gemma-4-26b",
"messages": [{"role": "user", "content": "Hello"}]
}'import os
from openai import OpenAI
client = OpenAI(
base_url="https://evanesca.app/api/v1",
api_key=os.environ["EVANESCA_API_KEY"],
)
answer = client.chat.completions.create(
model="gemma-4-26b",
messages=[{"role": "user", "content": "Hello"}],
)
print(answer.choices[0].message.content)import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://evanesca.app/api/v1",
apiKey: process.env.EVANESCA_API_KEY,
});
const answer = await client.chat.completions.create({
model: "gemma-4-26b",
messages: [{ role: "user", content: "Hello" }],
});
console.log(answer.choices[0].message.content);package main
import (
"fmt"
"io"
"net/http"
"os"
"strings"
)
func main() {
body := strings.NewReader(`{"model": "gemma-4-26b", "messages": [{"role": "user", "content": "Hello"}]}`)
req, err := http.NewRequest("POST", "https://evanesca.app/api/v1/chat/completions", 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": "chatcmpl-…",
"object": "chat.completion",
"model": "…",
"choices": [
{
"index": 0,
"message": { "role": "assistant", "content": "…" },
"finish_reason": "stop"
}
],
"usage": { "prompt_tokens": 9, "completion_tokens": 12, "total_tokens": 21 }
}{
"error": {
"message": "Insufficient credit balance. Add credit to your account to continue.",
"type": "insufficient_credit"
}
}