The SDKs
Two packages for two readers. You build with services.dev. Your buyers' developers get a typed client for your brand, generated from your Services.
There are two packages, for two readers.
| Package | Who reads it | What it holds |
|---|---|---|
services.dev | you, building | Service(), dev, check, the CLI, and services.dev/agent, a buyer for any brand |
| your brand's client | your buyers' developers | one typed function per operation your brand serves, named for your brand |
One command writes it from your manifest:
$ npx services.dev sdk
wrote sdk/index.js · sdk/index.d.ts · sdk/package.json
next import { connect } from './sdk/index.js'Every function is one operation row's SDK column, the same row that gives the API its route and MCP its tool. Each Service gets <service>.order(). The job, ask, event, watch and test-clock calls follow, each documented with its route and the error it throws:
export interface Client {
translation: {
/** Order a job, or get the offer: the price and the exact next call. A Service whose price has every answers a subscription, sbn_…. `POST /jobs` with service: "translation"; refused with ServicesError<'service.order'>. */
order(input: Order<"translation">): Promise<Job<"translation"> | Offer>
}
jobs: {
/** Read a job, now or after waiting up to wait seconds while it works or waits on a person. Waiting on a party, it answers at once with the link to hand them. `GET /jobs/:id`; refused with ServicesError<'job.get'>. */
get(id: JobId, params?: { wait?: number; expand?: string[] }): Promise<Job>
/** Wait up to 50 seconds for a job to finish, through any person's line. `GET /jobs/:id?wait=<seconds>`; refused with ServicesError<'job.wait'>. */
wait(id: JobId, params?: { wait?: number }): Promise<Job>
/** Accept a delivered job. It is final; a job nobody accepts is final 7 days after delivery. `POST /jobs/:id/accept`; refused with ServicesError<'job.accept'>. */
accept(id: JobId): Promise<Job>
…
}
asks: {
/** Reissue an open ask's link, to hand to its party again. The old one stops working at once. `POST /asks/:id/reissue`; refused with ServicesError<'ask.reissue'>. */
reissue(id: AskId, params?: { key?: string }): Promise<Ask>
}
…
}export interface ConnectOptions { key?: `sk_${'test' | 'live'}_${string}`; origin?: string; fetch?: (request: Request) => Promise<Response>; version?: "2026-10-25"; maxRetries?: number }
export declare function connect(options?: ConnectOptions): Clientkey is a buyer's key, sent as a bearer token: sk_test_ in the sandbox, sk_live_ for real money. On localhost, a call with none uses the test key dev printed. version pins the API version every answer carries in Services-Version. A retry reuses its first Idempotency-Key, so a retried order never orders twice. fetch lets a test run the brand in the same process.
const offer = await lingua.translation.order({ document: lease, to: "fr-CA", key: "lease" })
← OFFER · $49 per job, within 48 hours, charged on delivery. returned, not thrown
next order_translation {"document":{"url":"https://example.com/lease.pdf","name":"lease.pdf"},"to":"fr-CA","max_usd":49,"key":"lease"}
const job = await lingua.translation.order(offer.next.arguments)
← job_7Hq2 · working · $49.00 heldAn order without max_usd returns the offer as a value. Its next carries the arguments that order, key included. How every type in this file is made, and what it refuses at compile time, is Safe to depend on.
Each deploy publishes the client to npm under your brand's name, automatically, with nothing for you to sign or approve. services.dev sdk also writes the package into your folder, package.json already named for your brand, so a test can import it before you deploy.
Your buyers' developers use your brand's client: it's typed to your Services, and a wrong field or stage fails before it runs. An agent that buys from many brands uses services.dev/agent, which needs no generated code, only a brand's card (A buyer that is software).