PlatformsSDK

TypeScript

The generated TypeScript SDK: readable source, zero runtime dependencies, ESM, and the package the CLI and MCP server are built on.

The TypeScript SDK is the reference implementation of every feature on the SDKs overview, and the package the other TypeScript platforms import. This page covers what is specific to the TypeScript package.

Package

  • The npm name derives from your API's title. The Acme API produces acme. Set a scoped or different name per project under package names, for example @acme/api.
  • Zero runtime dependencies. The only entries in package.json are dev dependencies needed to build: typescript and @types/node.
  • ESM only. "type": "module", an exports map with an import entry, no require(). Node 18 or newer, every modern bundler, browsers, and edge runtimes.
  • Tree-shakeable: one module per resource and "sideEffects": false, so bundlers drop the resources you never call.
  • Ships as readable TypeScript source and compiles with npm run build. main, types, and exports point into dist/, which holds the compiled JavaScript, .d.ts declarations, and declaration maps.

What is exported

import {
  AcmeClient,          // the client
  environments,        // named servers, when the spec declares two or more
  VERSION,             // the package version
  unwrap,              // ApiResult -> data, or throw the typed error
  NotFoundError,       // one class per documented status
  UnexpectedApiError, TransportError, ValidationError,
  AccountStatus,       // enums as runtime values
  formatDebugEvent,
} from "acme";
import type { Account, AccountsListParams, AccountsListError, ApiResult, RequestOptions } from "acme";

Every schema is a type. Every operation has a <Resource><Method>Params type when it takes parameters and a <Resource><Method>Error union.

Environment variables

The client reads two, and neither is a credential:

VariableEffect
ACME_DEBUG=1Turns on debug logging when debug is not set.
ACME_WEBHOOK_KEYThe webhook signing key when webhookKey is not set.

Credentials are passed explicitly. That keeps the client safe in browsers and edge runtimes, where process.env may not exist. The generated CLI and MCP server are the pieces that read ACME_TOKEN and ACME_BASE_URL.

Idioms

  • Nothing throws on HTTP errors. Every call returns ApiResult<T, E> with a typed error union. unwrap opts into exceptions. See Results and errors.
  • PagePromise for list operations: for await walks every page, await returns one page. See Pagination.
  • readOnly becomes Omit. A referenced schema in a request position appears as Omit<Account, "id" | "balance">.
  • Enums are values. AccountStatus.FROZEN exists at runtime and AccountStatus is also the literal-union type.
  • Streams are AsyncIterable<SseEvent>. See Streaming.
  • Webhooks verify with WebCrypto, so unwrap is async and the same code runs on Node, browsers, edge runtimes, and Workers.
  • Async by nature. Every call returns a promise; there is no separate async client because there is nothing else to be.

Uploads

A multipart/form-data field is typed Blob. Pass a File (a Blob with a name, global in browsers and Node 20+) so the part carries a filename; a bare Blob is sent under the field name. The other fields of the body travel alongside as form parts, nested values as JSON. A raw binary body (application/octet-stream, image/png, ...) is a Blob too, sent under the spec's content type. fetch sets the multipart boundary.

await client.files.create({ file: new File([bytes], "report.pdf", { type: "application/pdf" }), purpose: "evidence" });

Unions

oneOf and anyOf become TypeScript unions of the variant types, narrowed the way you narrow any union: on a discriminator property, or with in checks. Nothing is wrapped and nothing is guessed at runtime; the payload is the JSON as sent.

Response metadata

Every ApiResult, success or failure, carries response: { status, headers, requestId }, so rate-limit headers, a Location, or a request id to quote are one property away without going through the onResponse hook. Error classes carry the same response.

const result = await client.accounts.get("acct_123");
result.response.headers.get("x-ratelimit-remaining");
result.response.requestId;

GraphQL

A GraphQL schema generates one method per query and mutation field on client.query and client.mutation. Arguments are the first argument and become variables. Every scalar field to depth 2 is selected by default, with a fragment per concrete type for unions and interfaces, and the optional second argument is a typed selection that replaces it and narrows the result: client.query.account({ id }, { id: true, owner: { email: true } }). Unions and interfaces take on: { TypeName: { ... } } and come back discriminated by __typename. A raw selection-set string is the escape hatch. Connection fields return a PagePromise like everything else. Errors in a 200 come back as GraphQLRequestError on the error side of the result, carrying the raw errors array. See Generate from GraphQL.

Transport

Everything rides on platform fetch. Pass fetch: to substitute your own for proxies, recording, or tests; connection reuse is whatever the runtime's fetch does (Node's undici and every browser pool keep-alive connections by default). Per-call signal composes with the per-attempt timeout, so an AbortController cancels a request, a paginated walk, or a stream. Streams are read incrementally through the same fetch.

What else is in the package

  • tests/*.test.mjs: one smoke test per operation on node:test and a node:http mock. npm test builds and runs them. No framework installed, nothing contacted.
  • api.md: the complete surface reference in one file: every method, parameter table, return type, and error class.
  • AGENTS.md: context for coding agents.
  • With the cli platform, src/cli.ts and the acme bin. With the mcp platform, src/mcp.ts, the acme-mcp bin, src/worker.ts, and wrangler.toml.

The files field publishes dist/, src/, and api.md.

On this page