Skip to content
Back to skills

Claude Sdk Deployment

ASecurity

Deploying Claude Agent SDK agents to production — wrapping the SDK in FastAPI with SSE streaming, session resume, cost tracking via ResultMessage, Docker/Railway/Fly config, ANTHROPIC_API_KEY handling, and bearer-auth patterns. Read when going from "it works locally" to "it's running on a server", when designing the request/response contract for an agent endpoint, or when investigating why deployments are slower or more expensive than expected.

  • 2 stars
  • 0 votes
  • 0 copies
  • 3 views
  • Added October 2, 2026
ai-agentspythongonodefastapidockerapifrontend

Works with

  • cli
  • api
  • mcp

Security analysis

A100/100

Scanned October 2, 2026

npx -y skills add Folken2/nuvel --skill claude-sdk-deployment --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Claude Sdk Deployment?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Claude Sdk Deployment
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/folken2-claude-sdk-deployment/badge)](https://www.skillsdirectory.com/skills/folken2-claude-sdk-deployment)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: claude-sdk-deployment
description: Deploying Claude Agent SDK agents to production — wrapping the SDK in FastAPI with SSE streaming, session resume, cost tracking via ResultMessage, Docker/Railway/Fly config, ANTHROPIC_API_KEY handling, and bearer-auth patterns. Read when going from "it works locally" to "it's running on a server", when designing the request/response contract for an agent endpoint, or when investigating why deployments are slower or more expensive than expected.
---

# Deploying a Claude Agent SDK agent

The SDK gives you `ClaudeSDKClient`, which is async-context-manager + async-iterator. It does not give you a server. The two questions deployment answers:

1. **What's the request/response contract?** (HTTP path, auth, streaming format)
2. **What does the server do with the SDK?** (per-request client, session state, cost tracking)

The nuvel-scaffolded `server.py` answers both with a FastAPI + SSE shape that's familiar to people who've deployed ADK or other agent runtimes. This skill explains why that shape was chosen and how to extend it.

## The default scaffold's contract

```
POST /run_sse/
Authorization: Bearer <API_KEY>      (if API_KEY env is set)
Content-Type: application/json
{ "prompt": "..." }

Response: text/event-stream
data: {"type": "assistant", "content": [{"type": "text", "text": "..."}]}
data: {"type": "assistant", "content": [{"type": "tool_use", "name": "...", "input": {...}}]}
data: {"type": "result", "session_id": "...", "total_cost_usd": 0.0042, "duration_ms": 4200, "num_turns": 3}
data: [DONE]
```

Each SSE event is one Claude SDK message serialized as JSON. The frontend can render text incrementally, surface tool calls as they happen, and capture cost on the final `result` event.

## One client per request

Don't reuse a single `ClaudeSDKClient` across requests. The async context manager is the unit of session — entering it starts a session, exiting it tears down the subprocess transport and any MCP server children. Build a fresh client per request:

```python
async def _sse_stream(prompt: str):
    async with get_client() as client:
        await client.query(prompt)
        async for msg in client.receive_response():
            yield serialize(msg)
```

Costs about 100ms of startup per request. If that matters for your latency budget, you'll want a client pool — but most agents are several-second responses where 100ms doesn't move the needle.

## Session resume (multi-turn from a stateless server)

A stateless HTTP server can still run multi-turn agents by threading `session_id`:

```python
@app.post("/run_sse/")
async def run_sse(body: dict):
    options = build_options()
    options.resume = body.get("session_id")  # None on first turn
    options.fork_session = False              # continue, don't fork

    async with ClaudeSDKClient(options=options) as client:
        await client.query(body["prompt"])
        async for msg in client.receive_response():
            yield serialize(msg)
```

The frontend keeps the `session_id` from the `result` event of turn N and sends it in turn N+1. The SDK loads conversation history transparently.

For multi-tenant deployments where session storage shouldn't live on the SDK's filesystem, use `session_store` with a `PostgresSessionStore` or similar — the SDK exposes a session-store interface for this.

## Cost tracking

The `ResultMessage` at the end of every turn includes `total_cost_usd` (cumulative for the session) and `num_turns`. Two patterns:

**Per-turn cost** = `total_cost_usd_now - total_cost_usd_previous_turn`. Subtract across turns yourself if you want per-turn billing.

**Per-session cap** = `max_budget_usd` in `ClaudeAgentOptions`. The SDK enforces it; once exceeded, further LLM calls fail. No plugin needed.

**Per-tenant aggregation** = persist the `result` event to a tracing table (the nuvel scaffold writes JSONL via `traces/trace_writer.py`) and aggregate at query time. Real production systems use a `total_cost_usd` column on a `sessions` table updated on each turn.

Don't try to match LiteLLM-style per-call pricing — the SDK gives you cumulative cost per session and that's enough for 95% of cases.

## ANTHROPIC_API_KEY handling

The SDK reads `ANTHROPIC_API_KEY` from the environment. You don't pass it explicitly; the underlying CLI does the lookup. Two operational patterns:

**Single-key deploys** — set `ANTHROPIC_API_KEY` in the deployment env (Railway dashboard, Fly secrets, etc.). The agent uses one key for all users.

**Per-tenant keys** — set `env={"ANTHROPIC_API_KEY": tenant_key}` in `ClaudeAgentOptions`. The SDK launches its subprocess transport with that env, so each session uses its own key. Right when you're charging tenants directly.

## Bearer auth on /run_sse/

The scaffold's pattern: if `API_KEY` env is set, require `Authorization: Bearer <API_KEY>`. If unset, no auth (good for local dev). Don't reach for OAuth or JWT until you have a real reason — most internal-tool agents are fine with a single shared bearer behind a VPN or Cloudflare Access.

When you do need real auth (multi-tenant, public), put it in front of the FastAPI app — not inside it. Cloudflare Access, an API gateway, or a simple JWT-validating proxy are all better than building auth into the agent server.

## Docker + Railway + Fly

The scaffold ships with `Dockerfile` (python:3.12-slim, copies the project, runs `python server.py`) and `railway.json` (starts via the Dockerfile, healthcheck on `/health`). For Fly, drop a `fly.toml` next to it; the Dockerfile is portable.

One Docker subtlety: the Claude Agent SDK spawns a Node.js subprocess (the underlying CLI). The slim base image works because `claude-agent-sdk` ships its own bundled Node — but if you switch to `python:3.12-alpine` you'll likely hit glibc issues. Stay on slim.

## Common deployment mistakes

- **Reusing a single `ClaudeSDKClient` across HTTP requests.** Eventually leaks subprocesses or stalls. Always per-request.
- **Forgetting `permission_mode`.** Default is `default` (interactive). Server deploys need `acceptEdits` or `dontAsk`. See `claude-sdk-permissions`.
- **No `max_budget_usd`.** A bug or prompt injection can cause runaway costs. Set a per-session cap; it's free and prevents disasters.
- **Sending tool internals to the client.** Serialize tool args carefully — they sometimes contain secrets (API keys, file contents) that shouldn't reach the browser.
- **Using `bypassPermissions` to skip prompts.** Lock-down with `dontAsk` + explicit `allowed_tools` instead.

## Quick checklist

Before merging a Claude Agent SDK agent to production:

- [ ] `permission_mode` is `acceptEdits` or `dontAsk`, not `default`
- [ ] `max_budget_usd` is set
- [ ] `ANTHROPIC_API_KEY` is in the deploy environment, not committed
- [ ] `API_KEY` is set if `/run_sse/` is internet-facing
- [ ] `/health` returns 200 and is wired to the platform's healthcheck
- [ ] Trace writer is on (`TRACE_ENABLED=true`) so you can debug after the fact
- [ ] One `ClaudeSDKClient` per request, not a singleton

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.