All skills
anthropics avatar

/claude-api

@8a1541c official
by Anthropicanthropics/skills179k stars
21,201

Reference for the Claude API / Anthropic SDK — model ids, pricing, params, streaming, tool use, MCP, agents, caching, token counting, model migration. TRIGGER — read BEFORE opening the target file; don't skip because it "looks like a one-liner" — whenever: the prompt names Claude/Anthropic in any form (Claude, Anthropic, Fable, Opus, Sonnet, Haiku, `anthropic`, `@anthropic-ai`, `claude-*`, `us.anthropic.*`, `[1m]`); the user asks about an LLM (pricing/model choice/limits/caching) — never answer from memory; OR the task is LLM-shaped with provider unstated (agent/MCP/tool-definition/multi-agent/RAG/LLM-judge/computer-use; generate/summarize/extract/classify/rewrite/converse over NL; debugging refusals/cutoffs/streaming/tool-calls/tokens). SKIP only when another provider is being worked on (overrides all triggers): OpenAI/GPT/Gemini/Llama/Mistral/Cohere/Ollama named in the query; OR `grep -rE 'openai|langchain_openai|google.generativeai|genai|mistralai|cohere|ollama'` over the project hits (run this grep FIRST if no provider named — don't Read the file).

Use this Skill: https://skilld.dev/gh/anthropics/skills/claude-api

This session only. Nothing lands on disk.

sharedmanaged-agents-client-patterns.md

≈3k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Managed Agents - Common Client Patterns

Patterns you'll write on the client side when driving a Managed Agent session, grounded in working SDK examples.

Code samples are TypeScript - other languages follow the same shape; see {lang}/managed-agents/README.md (cURL and C#: curl/managed-agents.md) for equivalents.


1. Lossless stream reconnect

Problem: SSE has no replay. If the connection drops mid-session, a naive reconnect re-opens the stream from "now" and you silently miss every event emitted in between.

Solution: on reconnect, fetch the full event history via events.list() before consuming the live stream, and dedupe on event ID as the live stream catches up.

const seenEventIds = new Set<string>()
const stream = await client.beta.sessions.events.stream(session.id)

// Stream is now open and buffering server-side. Read history first.
for await (const event of client.beta.sessions.events.list(session.id)) {
  seenEventIds.add(event.id)
  handle(event)
}

// Tail the live stream. Dedupe only gates handle() - terminal checks must run
// even for already-seen events, or a terminal event that was in the history
// response gets skipped by `continue` and the loop never exits.
for await (const event of stream) {
  if (!seenEventIds.has(event.id)) {
    seenEventIds.add(event.id)
    handle(event)
  }
  if (event.type === 'session.status_terminated') break
  if (event.type === 'session.status_idle' && event.stop_reason.type !== 'requires_action') break
}

2. processed_at - queued vs processed

Every event on the stream carries processed_at (ISO 8601), set when the event finishes processing. For client-sent events (user.message, user.interrupt, user.tool_confirmation) it's null while the event is queued behind earlier ones, and populated once the agent processes it - so the same event appears on the stream twice, once with null and once with a timestamp. (Exception: a user.interrupt sent while the session is paused at its budget is accepted and ignored - it never appears at all; see shared/managed-agents-events.md § Reaching a session budget.)

Three event types skip the queued phase: user.define_outcome, user.custom_tool_result, and user.tool_result are processed on receipt and echoed back with processed_at already populated. A pending -> acknowledged UI that assumes "first sighting is always null" will never clear for these - treat a populated processed_at on first sighting as immediately acknowledged.

for await (const event of stream) {
  if (event.type === 'user.message') {
    if (event.processed_at == null) onQueued(event.id)
    else onProcessed(event.id, event.processed_at)
  }
}

Use this to drive pending -> acknowledged UI state for anything you send. How you map a locally-rendered optimistic message to the server-assigned event.id is application-specific (typically via the return value of events.send() or FIFO ordering).


3. Interrupt a running session

Send user.interrupt as a normal event. The session keeps running until it reaches a safe boundary, then goes idle.

await client.beta.sessions.events.send(session.id, {
  events: [{ type: 'user.interrupt' }],
})

// Drain until the session is truly done - see Pattern 5 for the full gate.
for await (const event of stream) {
  if (event.type === 'session.status_terminated') break
  if (
    event.type === 'session.status_idle' &&
    event.stop_reason.type !== 'requires_action'
  ) break
}

Reference: interrupt.ts - sends the interrupt the moment it sees span.model_request_start, drains to idle, then verifies via sessions.retrieve().


4. tool_confirmation round-trip

When a call evaluates to ask - the tool has permission_policy: { type: 'always_ask' }, or it has { type: 'auto' } and the server reached no determination - the agent.tool_use / agent.mcp_tool_use event carries evaluated_permission === 'ask' and the session goes idle waiting for a decision. Respond with user.tool_confirmation.

for await (const event of stream) {
  if ((event.type === 'agent.tool_use' || event.type === 'agent.mcp_tool_use') && event.evaluated_permission === 'ask') {
    await client.beta.sessions.events.send(session.id, {
      events: [{
        type: 'user.tool_confirmation',
        tool_use_id: event.id,         // not a toolu_ id - use event.id
        result: 'allow',               // or 'deny'
        // deny_message: '...',        // optional, only with result: 'deny'
      }],
    })
  }
}

Key points:

  • tool_use_id is event.id (typically sevt_...), not a toolu_... ID.
  • result is 'allow' | 'deny'. Use deny_message to tell the model why you denied - it gets surfaced back to the agent.
  • Multiple pending tools: respond once per agent.tool_use / agent.mcp_tool_use event with evaluated_permission === 'ask'.
  • Gate on evaluated_permission === 'ask', not on the policy you configured - it covers always_ask and auto-indeterminate alike. Calls the server denies under auto (evaluated_permission === 'deny', evaluation.evaluated_permission.reason_code === 'high_risk') never enter this flow: the agent gets an error tool result and the session keeps running; sending a confirmation for one is a 400.
  • Log event.evaluation for audit (type + reason_code), and tolerate a type or reason_code you don't recognize - branch on known values, pass unknown ones through.

Reference: tool-permissions.ts.


5. Correct idle-break gate

Do not break on session.status_idle alone. The session goes idle transiently - e.g. between parallel tool executions, while waiting for a user.tool_confirmation, or while awaiting a user.custom_tool_result. Break when idle with a non-requires_action stop_reason (terminal, or budget_reached - resumable only by a budget update, so break unless you intend to change or remove the budget), or on session.status_terminated.

for await (const event of stream) {
  handle(event)
  if (event.type === 'session.status_terminated') break
  if (event.type === 'session.status_idle') {
    if (event.stop_reason.type === 'requires_action') continue // waiting on you - handle it
    break // end_turn, retries_exhausted, or budget_reached - see list below
  }
}

stop_reason.type values on session.status_idle:

  • requires_action - agent is waiting on a client-side event (tool confirmation, custom tool result). Handle it, don't break. Self-hosted exception: if the session went requires_action-idle with no pending agent.tool_use / agent.mcp_tool_use (ask) or agent.custom_tool_use to answer, the worker failed the claimed work item (typically a memory-store mount error, logged only on the worker host). Don't continue forever on that - surface it, fix the host, and send user.interrupt to re-queue the work (shared/managed-agents-self-hosted-sandboxes.md § Memory stores -> Troubleshooting).
  • retries_exhausted - terminal failure. Break, then check sessions.retrieve() for the error state.
  • end_turn - normal completion.
  • budget_reached - the session hit its spend cap and paused. Not terminal and not resumable by any event: change (typically raise) or remove the session's budget to resume, or treat it as done. A session.usage event with the final cost immediately precedes this idle. See shared/managed-agents-core.md § Session budgets.

6. Post-idle status-write race

The SSE stream emits session.status_idle slightly before the session's queryable status reflects it. Clients that break on idle and immediately call sessions.delete() or sessions.archive() will intermittently 400 with "cannot delete/archive while running."

Poll before cleanup:

let s
for (let i = 0; i < 10; i++) {
  s = await client.beta.sessions.retrieve(session.id)
  if (s.status !== 'running') break
  await new Promise(r => setTimeout(r, 200))
}
if (s?.status !== 'running') {
  await client.beta.sessions.archive(session.id)
} // else: still running after 2s - don't archive, let it settle or escalate

7. Stream-first, then send

Always open the stream before sending the kickoff event. Otherwise the agent may process the event and emit the first events before your consumer is attached, and you'll miss them.

const stream = await client.beta.sessions.events.stream(session.id)
await client.beta.sessions.events.send(session.id, {
  events: [{ type: 'user.message', content: [{ type: 'text', text: 'Hello' }] }],
})
for await (const event of stream) { /* ... */ }

The Promise.all([stream, send]) shape works too, but stream-first is simpler and has the same effect - the stream starts buffering the moment it's opened.


8. File-mount gotchas

The mounted resource has a different file_id than the file you uploaded. Session creation makes a session-scoped copy.

const uploaded = await client.beta.files.upload({ file, purpose: 'agent_resource' })
// uploaded.id         -> the original file
const session = await client.beta.sessions.create({
  /* ... */
  resources: [{ type: 'file', file_id: uploaded.id, mount_path: '/workspace/data.csv' }],
})
// session.resources[0].file_id !== uploaded.id  <- different IDs

Delete the original via files.delete(uploaded.id); the session-scoped copy is garbage-collected with the session. mount_path must be absolute - see shared/managed-agents-environments.md.


9. Secrets for non-MCP APIs and CLIs - keep them host-side via custom tools

Problem: you want the agent to call a third-party API or run a CLI that needs a secret (API key, token, service-account credential), but you can't or don't want to hand the secret to a vault.

First check: for cloud environments, the first-class answer is now a vault environment_variable credential - the agent's shell sees an opaque placeholder and the real secret is substituted at egress. See shared/managed-agents-tools.md -> Vaults. Use this pattern instead when that doesn't fit: self-hosted sandboxes (env-var credentials not yet supported there), clients that reject the placeholder via local format validation, secrets that must never leave your infrastructure, or calls that need host-side binaries.

Solution: move the authenticated call to your side. Declare a custom tool on the agent; when the agent emits agent.custom_tool_use, your orchestrator (the process reading the SSE stream) executes the call with its own credentials and responds with user.custom_tool_result. The container never sees the key.

// Agent template: declare the tool, no credentials
tools: [{ type: 'custom', name: 'linear_graphql', input_schema: { /* query, vars */ } }]

// Orchestrator: handle the call with host-side creds
for await (const event of stream) {
  if (event.type === 'agent.custom_tool_use' && event.name === 'linear_graphql') {
    const result = await linear.request(event.input.query, event.input.vars) // host's key
    await client.beta.sessions.events.send(session.id, {
      events: [{
        type: 'user.custom_tool_result',
        custom_tool_use_id: event.id,
        content: [{ type: 'text', text: JSON.stringify(result) }],
      }],
    })
  }
}

Same shape works for gh CLI, local eval scripts, or anything else that needs host-side auth or binaries.

Security note: this does not expose a public endpoint. agent.custom_tool_use arrives on the SSE stream your orchestrator already holds open with your Anthropic API key, and user.custom_tool_result goes back via events.send() under the same key. Your orchestrator is a client, not a server - nothing unauthenticated is listening.

Do not embed API keys in the system prompt or user messages as a workaround. Prompts and messages are stored in the session's event history, returned by events.list(), and included in compaction summaries - a secret placed there is durably persisted and readable via the API for the life of the session.

Source: SKILL.md on GitHub

1 warning2d5 checks · Risk SAFE
  • Gen Agent Trust Hub2d

    This skill is a developer reference for the Claude API and Anthropic SDKs. It includes some security considerations related to building agents with powerful capabilities like shell command execution and web fetching. While these present a potential surface for indirect prompt injection, the skill provides extensive security guidance, emphasizing sandboxing and input validation as mitigation strategies. All external resources and packages originate from trusted official sources.

  • Socket2d

    No alerts

  • Snyk2d

    Risk: LOW · No issues

  • Runlayer7mo

    12/26 files flagged

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at 8a1541c. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 3 days ago.

Activeupdated 3 days ago

README badge

README badge for anthropics/skills/claude-api

Reference for the Claude API and official Anthropic SDKs — model IDs, pricing, parameters, streaming, tool use, MCP, managed agents, caching, token counting, and model migration. Read this skill before opening a file that involves Claude, an Anthropic model, agent workflows, or LLM-shaped tasks with no specified provider.

Generated from the current SKILL.md.

Which Claude model should I use by default?
Use Claude Opus 4.8 (model ID: `claude-opus-4-8`) as the default. Also default to adaptive thinking (`thinking: {type: "adaptive"}`) for anything complex, and streaming for requests with long input, output, or high max_tokens.
What should I do if the project uses OpenAI or another non-Anthropic provider?
Stop and ask the user whether they want to switch the file to Claude or want a non-Claude implementation. Do not edit a non-Anthropic file with Anthropic SDK calls.
Should I use the official SDK or raw HTTP?
Use the official Anthropic SDK for your language whenever one exists (Python, TypeScript, Java, Go, Ruby, C#, PHP). Only use raw HTTP (curl, requests, fetch) if the user explicitly asks for it, the project is shell/cURL, or the language has no official SDK.
When should I use Managed Agents versus Claude API with tool use?
Use Managed Agents when you want Anthropic to run the agent loop and host a per-session container for tool execution (file ops, bash, code). Use Claude API with tool use for multi-step workflows where you control the orchestration and host the compute yourself.
Does this skill work with Amazon Bedrock, Google Vertex AI, or Microsoft Foundry?
Managed Agents is not available on those platforms. Use Claude API with tool use instead. Claude Platform on AWS (Anthropic-operated) has full feature parity with the first-party API.

Generated from the current SKILL.md. These answers refresh after source changes.