---
title: "A buyer that is software"
url: https://services.dev/docs/sdk/agent
---
# A buyer that is software

URL: https://services.dev/docs/sdk/agent

> services.dev/agent buys from any brand by its card. It asks the price, orders within a budget, waits through any person's line, and accepts.

`services.dev/agent` is a buyer in a few lines of code. It needs no generated client. It reads a brand's `/.well-known/agents.json`, connects to the MCP server the card names, and calls the same tools any agent would.

## Buy a job

```ts
import { connect } from 'services.dev/agent'
import translation from './services/translation.ts'

const lingua = connect('http://localhost:8787', { budget: 100 })
const lease = { url: 'https://example.com/lease.pdf', name: 'lease.pdf' }

const job = await lingua.order(translation, { document: lease, to: 'fr-CA' })
if (!('done' in job)) throw new Error(job.type === 'OFFER' ? job.price.sentence : job.reason)

const done = await job.done()
if (done.status !== 'delivered') throw new Error(`${done.id} ${done.status}`)
console.log(`${done.id} · delivered · ${done.output.document.name}`)

await job.accept()
```

Run it beside `npx services.dev`:

```txt
$ node buy.ts
job_7Hq2 · delivered · lease.fr-CA.pdf
job_7Hq2 · accepted · spent $49 of $100
```

## What it sends

`order()` sends the input without `max_usd`, so the first answer is the OFFER. When the price fits what's left of the budget, it sends the offer's `next` back unchanged, key included, so a retry never orders twice. When it doesn't fit, `order()` returns the offer for you to show a person.

```txt
→ GET /.well-known/agents.json
← 200 · lingua · mcp http://localhost:8787/mcp
→ initialize
← lingua · "Without max_usd you get an OFFER: send its next call unchanged to order."
→ tools/list
← order_translation  "Translation: send a document and a language; get a document back. $49 per job, within 48 hours, charged on delivery. AI delivers every job."
  get_job · accept_job · send_back_job · cancel_job · and 8 more
→ order_translation {document: document.pdf, to: "fr-CA", key: "lease-fr-CA"}
← OFFER · $49 per job, within 48 hours, charged on delivery.
  next: order_translation {document: document.pdf, to: "fr-CA", max_usd: 49, key: "lease-fr-CA"}
→ order_translation {document: document.pdf, to: "fr-CA", max_usd: 49, key: "lease-fr-CA"}
← job_7Hq2 · working · $49.00 held
→ get_job {id: "job_7Hq2", wait: 50}
← job_7Hq2 · delivered · document.fr-CA.pdf · $49.00 charged
  stage: review · payment: charged $49
→ accept_job {id: "job_7Hq2"}
← job_7Hq2 · accepted · document.fr-CA.pdf · $49.00 final
```

## Every call is a row

```ts
export const METHODS = {
  order: 'service.order',
  get: 'job.get',
  done: 'job.wait',
  accept: 'job.accept',
  sendBack: 'job.send_back',
  cancel: 'job.cancel',
} as const satisfies Record<Exclude<keyof Agent, 'spent'> | Exclude<keyof Bought, 'id' | 'job'>, OpId>
```

Each function calls its row's MCP tool, so the agent can do exactly what the tools allow. It can't do what only a person does ([What an agent can never do](https://services.dev/docs/mcp/never.md)).

## When a person is needed

`done()` long-polls until the job is delivered, accepted or canceled. A job waiting on a party comes back at once, with the link only that person can use:

```txt
const job = await agent.order('engagement', { client: ada, matter: 'a lease review' })
← job_7Hq2 · working · $120.00 held

const now = await job.done()
← job_7Hq2 · waiting · on party · $120.00 held
  next    {"do":"hand_to_person","url":"http://localhost:8787/a/ask_7Hq2?t=8FaWT5EoCj4h-uMyjlXp3_","to":"sign"}

agent.spent
← 120
```

Hand the link on. Once they act, `done()` waits through the rest. Over the buyer key's cap, the offer's `next` is checkout, a person's step, so the agent returns the offer without spending.

Worked in: [the agent's order](https://services.dev/docs/examples/sales/agent.md), one deal from the OFFER to the payment.

## Refusals are values

Your brand's typed client throws a refusal. The agent returns it: `order()` answers a bought job, an offer or a refusal, so a loop can tell the three apart and decide what to do next.

## Options

```ts
export interface AgentOptions {
  /** The most this agent spends in all, in dollars. */
  budget?: number
  /** The buyer key, sent as Authorization: Bearer. On localhost dev takes a call with none. */
  key?: string
  /** How requests go out: a recorder, or a server in the same process. */
  fetch?: (request: Request) => Promise<Response>
}
```

`budget` caps what the agent spends across every order, and `spent` reads it back.

---

- Up: [The SDKs](https://services.dev/docs/sdk.md)
- Previous: [Safe to depend on](https://services.dev/docs/sdk/types.md)
- Next: [The customer portal](https://services.dev/docs/app/buyers.md)
