Can I run the Claude API on Bun? Yes — @anthropic-ai/sdk has worked on Bun since SDK v1.0 (mid-2024) and runs unmodified on Bun 1.2+. In our local benchmarks on an M4 Mac mini, Bun cold-starts a Claude API process roughly 3× faster than Node.js 22 (about 47ms vs 142ms to first token request) and warm calls are ~2× faster. There is one real caveat: Bun's fetch implementation has had subtle differences in streaming abort semantics, so streaming + AbortController deserves a closer look. Below: the working code, the numbers, the gotchas, and the deploy targets that actually matter.
How to call Claude API from Bun
Bun ships its own runtime, package manager, and TypeScript executor. You don't need tsx, ts-node, or a tsconfig.json to run a .ts file. Install the SDK with Bun's package manager:
bun init -y
bun add @anthropic-ai/sdk
Then a complete hello.ts:
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY,
});
const start = performance.now();
const message = await client.messages.create({
model: "claude-sonnet-4-5",
max_tokens: 256,
messages: [
{ role: "user", content: "In one sentence: what is Bun?" },
],
});
console.log(message.content[0].type === "text" ? message.content[0].text : "");
console.log(`elapsed: ${(performance.now() - start).toFixed(0)}ms`);
Run it directly:
ANTHROPIC_API_KEY=sk-ant-... bun run hello.ts
No build step. No tsc. No node --loader. The single-binary story is the headline reason teams switch — but the runtime behavior underneath is where the cold-start delta comes from.
Cold-start benchmarks: Bun vs Node.js
We measured a minimal "import SDK, send one request, read the response" script on the same machine (M4 Mac mini, 32 GB, macOS 15.4) over 50 runs each. Cold start = process spawn to first byte returned from messages.create. Warm = re-using the same process for the next call.
| Metric | Node.js 22.13 | Bun 1.2.10 | Delta |
|---|---|---|---|
| Cold start (p50) | 142 ms | 47 ms | 3.0× faster |
| Cold start (p95) | 188 ms | 71 ms | 2.6× faster |
| Warm call (p50) | 28 ms | 12 ms | 2.3× faster |
| RSS after 1 req | 86 MB | 41 MB | 2.1× lower |
bun add vs npm i |
11.4 s | 0.9 s | 12× faster |
The cold-start gap is mostly module resolution and TS transpilation overhead. Node has to pass .ts through tsx or a loader; Bun parses TS natively in its JavaScriptCore-based runtime. For long-running servers the cold-start win amortizes to zero — but for scheduled jobs, CLI tools, queue workers that scale to zero, and serverless containers, the delta is real money. A nightly batch that fires 200 invocations saves ~19 seconds of wall clock and proportional CPU billing.
The warm-call delta is smaller and dominated by network RTT to api.anthropic.com, so don't expect Bun to magically halve your inference latency. It won't. The runtime overhead is a few percent of the total in steady state.
The fetch caveat
The Anthropic SDK uses the platform fetch. Bun's fetch is not Node's undici — it's a custom implementation written in Zig. For 95% of workloads they behave identically. The 5% that has bitten people:
- Streaming abort semantics. Calling
controller.abort()mid-stream in Bun used to leave the underlying TCP socket in a half-closed state on some versions. Fixed in Bun 1.1.30+, but if you're on an older Bun and you abort a long Claude stream (e.g. user navigated away), verify the connection actually closes. Add asignalto every streaming call and test withlsof -p $(pgrep bun)if you suspect leaks. Response.bodylock contention. Bun is stricter about reading the body twice. If you logresponse.bodyand then pass it to the SDK, you'll get a "stream already locked" error that Node tolerated.- Custom
fetchinjection. If you pass your ownfetchtonew Anthropic({ fetch: myFetch })for retry/proxy logic, Bun's runtime gives youglobalThis.fetchfrom Bun, notundici. Most polyfills work, butnode-fetch@3will not — drop it and use the platformfetch.
For a streaming example that handles abort cleanly, see our streaming guide. The pattern below is the minimal Bun version:
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
const controller = new AbortController();
// Optional: abort if user disconnects, timeout fires, etc.
setTimeout(() => controller.abort(), 30_000);
const stream = await client.messages.stream(
{
model: "claude-sonnet-4-5",
max_tokens: 1024,
messages: [{ role: "user", content: "Explain Bun's runtime in 3 bullets." }],
},
{ signal: controller.signal },
);
for await (const event of stream) {
if (event.type === "content_block_delta" && event.delta.type === "text_delta") {
process.stdout.write(event.delta.text);
}
}
const final = await stream.finalMessage();
console.log(`\ntokens: in=${final.usage.input_tokens} out=${final.usage.output_tokens}`);
That same script under Node would need a tsx shim. Under Bun, bun run stream.ts and you're done.
Building a Claude-powered product? The Cost Optimization Masterclass ($59) walks through prompt caching, model routing, and the exact runtime + deploy choices that cut a typical Claude bill by 60–80%. Pairs well with the runtime decisions on this page.
Package compatibility: @anthropic-ai/sdk on Bun
The official SDK lists Node 18+ as supported, but Bun has been a tested target since SDK v1.0 (the rewrite that moved off node-fetch). Concretely:
| Feature | Bun status | Notes |
|---|---|---|
messages.create |
Works | Identical to Node |
messages.stream |
Works | Use signal for abort safety |
| Tool use / function calling | Works | No runtime differences |
| Prompt caching | Works | cache_control headers passthrough is fine |
| Files API | Works | Bun.file() returns a Blob-compatible value |
| Batch API | Works | JSONL streaming behaves correctly |
| Web search tool | Works | Server-side, runtime-agnostic |
extra_headers |
Works | Useful for error handling middleware |
Two ecosystem packages worth noting:
@anthropic-ai/vertex-sdk— works on Bun. The Google auth flow usesgoogle-auth-library, which is pure JS and runtime-agnostic.@anthropic-ai/bedrock-sdk— works, but AWS SDK v3 is heavy (~30 MB resolved). Bun's package install speed makes the bloat less painful than under npm.
If you wrap the SDK with retry logic or cost monitoring, test under both runtimes before shipping. The differences are rare but real, and "works on my Mac under Bun" is a known failure mode when production runs Node.
When NOT to use Bun
Bun's wins evaporate in two important cases:
- Vercel Edge / Cloudflare Workers. These are V8 isolates running Web Standards APIs — there's no process spawn, no module load, no TS transpile. Cold start is in the single-digit milliseconds because the isolate is pre-warmed. Bun adds nothing here; you're not even running Bun, you're running V8 with the Workers runtime. Use the SDK as-is, deploy with
wranglerorvercel deploy, and ignore this article. - AWS Lambda with provisioned concurrency. If you've already paid for warm instances, the Node 22 runtime is a known quantity, has first-class CloudWatch integration, and the cold-start savings don't apply. Bun on Lambda is possible via custom runtime layers, but the operational surface area isn't worth a 100ms saving you don't need.
Where Bun shines: long-running servers (bun --hot), scheduled jobs, scale-to-zero containers, CLI tools, and local dev. The local-dev story alone (no tsx, instant install, built-in .env, built-in test runner) is enough for most teams.
Production deployment options
Bun is a first-class runtime on most modern platforms now. Concrete options as of May 2026:
| Platform | Bun support | Notes |
|---|---|---|
| Railway | Native | Auto-detects bun.lockb; sets bun start |
| Fly.io | Native | Use flyctl launch with the Bun preset |
| Render | Native | "Bun" runtime in the dashboard |
| Northflank | Native | Buildpack autodetect |
| Docker anywhere | Use oven/bun:1.2-alpine |
80 MB image vs ~150 MB for node:22-alpine |
| Vercel (Node functions) | Build-time only | Functions still run on Node |
| Cloudflare Workers | N/A | Workers runtime, not Bun (and that's fine) |
A minimal Dockerfile for a Bun + Claude service:
FROM oven/bun:1.2-alpine
WORKDIR /app
COPY package.json bun.lockb ./
RUN bun install --frozen-lockfile --production
COPY . .
ENV NODE_ENV=production
EXPOSE 3000
CMD ["bun", "run", "src/server.ts"]
Image size, install time, and cold start all tilt in Bun's favor. The ops cost story is small but compounds — if you run 50 small services, Bun's lower memory floor (~40 MB vs ~85 MB per process) lets you pack roughly 2× more on the same VM.
Frequently Asked Questions
Does the Anthropic SDK officially support Bun?
The SDK's stated support matrix is "Node 18+, Deno, Bun, modern browsers." Bun has been continuously tested against the SDK since v1.0. There is no separate @anthropic-ai/sdk-bun package — the same package works everywhere.
Can I use prompt caching from Bun?
Yes. Prompt caching is a server-side feature controlled by request headers and cache_control blocks in the message body. The runtime is irrelevant. Bun has no effect on cache hit rate; that's a function of how you structure your prompt prefix.
Will Bun reduce my Claude API bill?
No, not directly. Token counts and per-token pricing are identical regardless of runtime. Bun reduces infrastructure cost (compute, memory, cold-start latency on scale-to-zero platforms), not API cost. To cut the API bill itself, prompt caching and model routing are the real levers.
Is bun --hot reliable enough for production Claude services?
bun --hot is for development — it reloads on file change. For production, use plain bun run under a process supervisor (systemd, PM2, or your platform's default). --hot keeps state across reloads, which can mask bugs that surface in a fresh process.
What about Deno for Claude API workloads?
Deno also runs the SDK fine and has similar cold-start characteristics to Bun (both skip the TS-transpile step Node requires). Bun's edge over Deno is npm compatibility — most teams have a package.json and don't want to rewrite imports. If you're greenfield with no npm dependencies, Deno is competitive. Otherwise, Bun is the lower-friction pick.