@apicity/anthropic
v0.1.0
Published
Anthropic / Claude provider for messages, batches, models, files, and admin APIs.
Maintainers
Readme
@apicity/anthropic
Anthropic / Claude provider for messages, batches, models, files, and admin APIs.
Installation
npm install @apicity/anthropic
# or
pnpm add @apicity/anthropicQuick Start
import { anthropic as createAnthropic } from "@apicity/anthropic";
const anthropic = createAnthropic({ apiKey: process.env.ANTHROPIC_API_KEY! });Real-world example: multi-turn TypeScript review with system prompt + few-shot priming
The single biggest knob the Anthropic Messages API gives you is the
messages array itself: every prior turn is grist for the next one.
Pair that with a strict system prompt to lock the output format and
one priming exchange to demonstrate the format, and you get a
deterministic-looking single-line bug reviewer out of claude-sonnet-4-6
without any tools, no JSON-mode, no fine-tune. Every token, model id,
and content block below is mined from
tests/recordings/anthropic_2966493235/messages-code-review_891889396/,
the HAR replayed by
tests/integration/anthropic-messages-code-review.test.ts
— recorded straight against https://api.anthropic.com/v1/messages with
the typed @apicity/anthropic client.
import { anthropic as createAnthropic } from "@apicity/anthropic";
import type {
AnthropicMessageResponse,
AnthropicTextBlock,
} from "@apicity/anthropic";
const anthropic = createAnthropic({
apiKey: process.env.ANTHROPIC_API_KEY!,
});
// 1. The `system` field is a hard contract for response shape — Claude
// treats it as higher-priority instruction than anything in
// `messages`. Spelling out the exact line format here ("BUG: <one
// sentence>", no preamble, no Markdown) is what stops the model
// from wrapping the answer in "Sure! Here's the bug:" or fenced
// code, which it does by default.
//
// 2. The `messages` array is a *transcript*, not a single prompt. The
// one-shot user→assistant pair below primes the format on a known
// bug (off-by-one in a loop bound) so the third turn answers in
// the same shape on a code snippet the model has not seen before.
// This is few-shot prompting; no fine-tuning required.
const result: AnthropicMessageResponse = await anthropic.v1.messages({
model: "claude-sonnet-4-6",
max_tokens: 256,
system:
"You are a senior TypeScript reviewer. Reply with exactly one line " +
"in the form: 'BUG: <one-sentence summary>'. No preamble, no code, " +
"no Markdown.",
messages: [
{
role: "user",
content:
"Review this:\n```ts\nfunction firstNonEmpty(xs: string[]): string {\n" +
" for (let i = 0; i <= xs.length; i++) {\n" +
" if (xs[i]) return xs[i];\n" +
" }\n return '';\n}\n```",
},
{
role: "assistant",
content:
"BUG: The loop condition `i <= xs.length` reads one past the " +
"last index, so `xs[xs.length]` is dereferenced as undefined.",
},
{
role: "user",
content:
"Now review this one the same way:\n```ts\n" +
"async function readAll(stream: ReadableStream<Uint8Array>): " +
"Promise<Uint8Array> {\n" +
" const reader = stream.getReader();\n" +
" const chunks: Uint8Array[] = [];\n" +
" while (true) {\n" +
" const { done, value } = await reader.read();\n" +
" if (done) break;\n chunks.push(value);\n }\n" +
" return Buffer.concat(chunks);\n}\n```",
},
],
});
// 3. `content` is always an array of typed blocks, never a single
// string. This is the same shape Claude returns when there are
// `tool_use`, `thinking`, or `image` blocks in the response —
// treating it uniformly here means tool-using and thinking
// workflows drop in without restructuring the read path.
const text = result.content
.filter((b): b is AnthropicTextBlock => b.type === "text")
.map((b) => b.text)
.join("")
.trim();
console.log(text);
// → "BUG: The reader is never released via `reader.releaseLock()`
// (especially on error), leaving the stream permanently locked."
console.log(
`model=${result.model} stop=${result.stop_reason} ` +
`in=${result.usage.input_tokens} out=${result.usage.output_tokens}`,
);
// → "model=claude-sonnet-4-6 stop=end_turn in=268 out=33"Notes
- The
systemprompt is treated as a soft-but-strong constraint on every turn, not just the first — Claude re-applies it as the conversation lengthens, so the format won't drift after 10 turns the way a single user-message instruction will. Pass it as a string for short rules; pass it as[{ type: "text", text, cache_control: { type: "ephemeral" } }]to mark it as cacheable when you reuse the same long system prompt across many requests. - Few-shot priming via a fake
assistantturn is the cheapest reliable way to lock output formatting withouttoolsor structured-output APIs. Claude does not distinguish between turns it actually generated and turns you wrote — both are equally authoritative context. Keep priming turns short and exact; if yours doesn't match the system rule character-for-character (e.g. a trailing period the system says shouldn't be there), the model will hedge. result.contentis a(AnthropicTextBlock | AnthropicToolUseBlock | AnthropicThinkingBlock | …)[]discriminated union. Filter onb.type === "text"before reading.text; onb.type === "tool_use"before reading.input; onb.type === "thinking"before reading.thinking. Claude can interleave them in one response — e.g. a leading text block, then a thinking block, then a tool_use — so always iterate, never assumecontent[0].usage.input_tokensincludes the entire transcript on every turn, so a 10-turn conversation pays for the system prompt + 10 turns of history every time. To amortize a long system prompt (>1024 tokens) across many calls, mark itcache_control: ephemeraland watchusage.cache_read_input_tokensrise on calls 2–N.- Errors throw
AnthropicErrorwithstatus, the parsedbody, and the upstreamerrorType("authentication_error", "rate_limit_error", "overloaded_error", …). Wrap withwithRetryfrom@apicity/anthropicfor429and529 overloaded_error, which Claude returns under sustained load. Pair withwithFallbackto roll over toclaude-haiku-4-5for non-critical paths. - Point the same call at a Claude-compatible gateway by passing
baseURL(and afetchwrapper if the backend uses a different auth header thanx-api-key) tocreateAnthropic. The factory's request shape is upstream-faithful, so anything that speaks the Anthropic Messages wire format — Bedrock proxies, third-party gateways, local mocks — works without touching call sites.
API Reference
26 endpoints across 4 groups. Each method mirrors an upstream URL path.
files
DELETE https://api.anthropic.com/v1/files/{fileId}
const res = await anthropic.v1.files.del({ /* ... */ });Source: packages/provider/anthropic/src/anthropic.ts
GET https://api.anthropic.com/v1/files/{fileId}/content
const res = await anthropic.v1.files.content({ /* ... */ });Source: packages/provider/anthropic/src/anthropic.ts
GET https://api.anthropic.com/v1/files
const res = await anthropic.v1.files.list({ /* ... */ });Source: packages/provider/anthropic/src/anthropic.ts
GET https://api.anthropic.com/v1/files/{fileId}
const res = await anthropic.v1.files.retrieve({ /* ... */ });Source: packages/provider/anthropic/src/anthropic.ts
POST https://api.anthropic.com/v1/files
const res = await anthropic.v1.files({ /* ... */ });Source: packages/provider/anthropic/src/anthropic.ts
messages
DELETE https://api.anthropic.com/v1/messages/batches/{batchId}
const res = await anthropic.v1.messages.batches.del({ /* ... */ });Source: packages/provider/anthropic/src/anthropic.ts
GET https://api.anthropic.com/v1/messages/batches
const res = await anthropic.v1.messages.batches.list({ /* ... */ });Source: packages/provider/anthropic/src/anthropic.ts
GET https://api.anthropic.com/v1/messages/batches/{batchId}/results
const res = await anthropic.v1.messages.batches.results({ /* ... */ });Source: packages/provider/anthropic/src/anthropic.ts
GET https://api.anthropic.com/v1/messages/batches/{batchId}
const res = await anthropic.v1.messages.batches.retrieve({ /* ... */ });Source: packages/provider/anthropic/src/anthropic.ts
POST https://api.anthropic.com/v1/messages
const res = await anthropic.v1.messages({ /* ... */ });Source: packages/provider/anthropic/src/anthropic.ts
POST https://api.anthropic.com/v1/messages
const res = await anthropic.v1.messages({ /* ... */ });Source: packages/provider/anthropic/src/anthropic.ts
POST https://api.anthropic.com/v1/messages/batches
const res = await anthropic.v1.messages.batches({ /* ... */ });Source: packages/provider/anthropic/src/anthropic.ts
POST https://api.anthropic.com/v1/messages/batches/{batchId}/cancel
const res = await anthropic.v1.messages.batches.cancel({ /* ... */ });Source: packages/provider/anthropic/src/anthropic.ts
POST https://api.anthropic.com/v1/messages/count_tokens
const res = await anthropic.v1.messages.countTokens({ /* ... */ });Source: packages/provider/anthropic/src/anthropic.ts
POST https://api.anthropic.com/v1/messages
const res = await anthropic.v1.messages({ /* ... */ });Source: packages/provider/anthropic/src/anthropic.ts
POST https://api.anthropic.com/v1/messages/batches
const res = await anthropic.v1.messages.batches({ /* ... */ });Source: packages/provider/anthropic/src/anthropic.ts
POST https://api.anthropic.com/v1/messages/count_tokens
const res = await anthropic.v1.messages.countTokens({ /* ... */ });Source: packages/provider/anthropic/src/anthropic.ts
models
GET https://api.anthropic.com/v1/models
const res = await anthropic.v1.models.list({ /* ... */ });Source: packages/provider/anthropic/src/anthropic.ts
GET https://api.anthropic.com/v1/models/{modelId}
const res = await anthropic.v1.models.retrieve({ /* ... */ });Source: packages/provider/anthropic/src/anthropic.ts
skills
DELETE https://api.anthropic.com/v1/skills/{skillId}
const res = await anthropic.v1.skills.del({ /* ... */ });Source: packages/provider/anthropic/src/anthropic.ts
DELETE https://api.anthropic.com/v1/skills/{skillId}/versions/{version}
const res = await anthropic.v1.skills.versions.del({ /* ... */ });Source: packages/provider/anthropic/src/anthropic.ts
GET https://api.anthropic.com/v1/skills
const res = await anthropic.v1.skills.list({ /* ... */ });Source: packages/provider/anthropic/src/anthropic.ts
GET https://api.anthropic.com/v1/skills/{skillId}
const res = await anthropic.v1.skills.retrieve({ /* ... */ });Source: packages/provider/anthropic/src/anthropic.ts
GET https://api.anthropic.com/v1/skills/{skillId}/versions
const res = await anthropic.v1.skills.versions.list({ /* ... */ });Source: packages/provider/anthropic/src/anthropic.ts
POST https://api.anthropic.com/v1/skills
const res = await anthropic.v1.skills.create({ /* ... */ });Source: packages/provider/anthropic/src/anthropic.ts
POST https://api.anthropic.com/v1/skills/{skillId}/versions
const res = await anthropic.v1.skills.versions.create({ /* ... */ });Source: packages/provider/anthropic/src/anthropic.ts
Middleware
import { anthropic as createAnthropic, withRetry } from "@apicity/anthropic";
const anthropic = createAnthropic({ apiKey: process.env.ANTHROPIC_API_KEY! });
const models = withRetry(anthropic.get.v1.models, { retries: 3 });Part of the apicity monorepo.
License
MIT — see LICENSE.
