CLI, MCP & SDKsSDKs

TypeScript

Generate a typed, zero-dependency TypeScript client for your API.

Typeship generates a typed TypeScript client for your API as a focused, zero-dependency npm package. This page covers the TypeScript-specific surface; shared client behavior is on the SDK overview.

CLI and MCP packages are generated and released separately.

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

  • Awaited calls return errors as data. Every awaited call returns ApiResult<T, E> with a typed error union. unwrap opts into exceptions. See Results and errors.
  • PagePromise for list operations: await returns one page's ApiResult; for await walks every page and throws the typed error if a page fails. See Pagination.
  • Directional models stay honest. A component whose request and response contracts differ becomes AccountWrite and AccountRead; identical contracts keep the clean Account name.
  • Enums are values. Request enums are closed. Response enums preserve documented autocomplete while accepting (string & {}), so a new server value does not make a successful response impossible to represent.
  • 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, rawBody }, so rate-limit headers, a Location, a request id to quote, or an unmodeled future response 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("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.
  • CLI and MCP files are never included in this package. They belong to their own Targets and destination repositories.

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

On this page