Create a message in the Anthropic Messages shape, with the Anthropic library.
POST https://opendunes.com/v1/messagesBilled, with an API key. The request and the answer are the Anthropic Messages shape: point the Anthropic library at https://opendunes.com and it runs unchanged but for the key, which it sends in x-api-key.
import os
from anthropic import Anthropic
client = Anthropic(
base_url="https://opendunes.com",
api_key=os.environ["OPENDUNES_API_KEY"],
)
message = client.messages.create(
model="anthropic/claude-sonnet-5",
max_tokens=512,
system="You answer Souk customers in French.",
messages=[{"role": "user", "content": "Quels sont vos horaires le vendredi ?"}],
)
print(message.content[0].text)Every field travels to the model as you sent it — thinking, output_config, tools and the rest. The answer is the model's, with its usage.cost in DA; it carries X-Generation-Id. The model is named lab/model, like every route, and any text model answers here, not only Claude.
| the model | any text model, a preset (@preset/<slug>) or a router (opendunes/auto) |
| helpers | opendunes:web_search, opendunes:web_fetch, opendunes:bash, opendunes:shell, opendunes:tool_search and the others in tools. opendunes:bash works here and not on Responses; opendunes:apply_patch works on Responses and not here |
| streaming | "stream": true streams the Anthropic events; the usage rides the closing event — see Streaming |
| the response cache | X-OpenDunes-Cache: true — see Response caching |
Files the bash or shell helper leaves are read at Containers.
An error is answered at its status in the Anthropic shape — type: "error", error.type in the Anthropic library's words, error.message, error.error_type — so the Anthropic library raises its own error class for that status. A model that is not on sale is a 404 not_found_error, and nothing is charged.