Cookbook

Recipes for real applications

Short, working patterns for the things production apps actually do. Every snippet works with any model on the platform unless it says otherwise.

Streaming

Set "stream": true and read server-sent events — tokens arrive as content_block_delta events, exact usage in message_delta at the end. With the SDKs it's one flag:

# Python with client.messages.stream( model="claude-sonnet-5", max_tokens=1024, messages=[{"role": "user", "content": "Write a haiku about latency."}], ) as stream: for text in stream.text_stream: print(text, end="", flush=True)

If your client disconnects mid-stream, generation stops upstream and you are only billed for what was produced.

Tool use

Declare tools, let the model call them, return results — the standard tool-use loop, on every model that supports tools:

msg = client.messages.create( model="claude-sonnet-5", max_tokens=1024, tools=[{ "name": "get_weather", "description": "Current weather for a city.", "input_schema": {"type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"]}, }], messages=[{"role": "user", "content": "What's the weather in Oslo?"}], ) # msg.stop_reason == "tool_use" → run the tool, send a tool_result block back

Prompt caching — the biggest bill cut available

If your requests repeat a long prefix (system prompt, document, tool definitions), cache it. Cached tokens bill at 10% of the input rate; writing costs 1.25× once (2× for a 1-hour cache). Mark the boundary with cache_control:

system=[{ "type": "text", "text": LONG_SYSTEM_PROMPT_OR_DOCUMENT, "cache_control": {"type": "ephemeral"}, # 5-minute cache; {"type":"ephemeral","ttl":"1h"} for 1 hour }]

On models with automatic caching (see the pricing table) you don't even do that — repeated prefixes are detected and billed at the cache-read rate with no write fee. Check usage.cache_read_input_tokens in responses to see it working. Models that support neither simply bill normal input — a cache_control marker never errors.

The priority lane

When a request is latency-critical, ask for priority capacity. It bills at 2× the token prices — and only when it's actually served fast; models without priority support run and bill standard:

client.messages.create( model="gpt-5.5", max_tokens=256, extra_body={"speed": "fast"}, # or header x-ares-speed: fast messages=[...], )

Count tokens before you send

curl https://api.projectares.ai/v1/messages/count_tokens \ -H "x-api-key: sk-ares-YOUR_KEY" -H "content-type: application/json" \ -d '{"model": "claude-sonnet-5", "messages": [{"role": "user", "content": "…"}]}' # → {"input_tokens": 42}

Projects: split telemetry by app

Create a project per app or environment in the console and mint each API key inside one. Usage, logs, and CSV exports then break down per project — so "staging" can't hide inside "production". Keys can also carry an expiry and a monthly spend cap.

Handling errors & retries

Errors are one consistent shape: {"type": "error", "error": {"type", "message"}}. Retry overloaded_error (529) and api_error (500/502) with exponential backoff; treat rate_limit_error (429) as backpressure; don't retry 4xx. The SDKs do this automatically.

import anthropic try: msg = client.messages.create(...) except anthropic.RateLimitError: # plan rpm or credit exhaustion — back off ... except anthropic.APIStatusError as e: # unified Ares error, e.status_code + e.message ...

Survive model retirements automatically

Deprecated models keep working but return an x-ares-deprecation header with a sunset date and replacement id. Log it, or resolve your model at startup from /v1/models and prefer replacement when status == "deprecated" — your app then rides every upgrade without a deploy.