---
title: "Start a Service API"
url: /quickstart/service-api
---

# Start a Service API

> A live API that sells an outcome: each Customer's annual report, drafted, checked, signed and filed, charged once when the Customer accepts it.

You need Node 20 and a GitHub account. The sample answers on `filings.apis.dev`; your first deploy answers at once on a free Host, and your own domain is one line.

1. **Start the folder**

   ```sh title="Terminal"
   $ npx apis.dev init service-api
   ✓ wrote definition.ts: a Service API
     next: npx apis.dev dev
   ```

   `init` writes a [Service API](/patterns/service-api.md) with one case, its moves and the price of its outcome.

2. **Write the file**

   ```ts title="definition.ts"
   import { define, workflow, agentic } from 'apis.dev'

   /** Each Customer's annual report, drafted, signed and filed, charged once when the Customer accepts it. */
   export default define({
     name: 'filings', domain: 'filings.apis.dev',
     Filing: {                                     // Noun: the work your buyer watches
       company: 'string', year: 'number',
       status: {                                   // Verbs: one per move
         order: [],
         draft: { in: 'ordered', run: workflow(async ($, filing) => {
           const { form } = await $.generative('Draft the annual report for {company}', filing,
             { returns: { form: 'string' } })
           await $.human('Check the draft', { form }, { role: 'paralegal' })  // unseen: a step
           return { form }
         }) },
         sign: 'drafted',                          // seen: the Customer's turn
         file: { in: 'signed', run: agentic('File the signed report with the state') },
         accept: 'filed', reject: 'filed',         // the Customer accepts, or it is redone
       },
     },
     on: { 'Filing.ordered': 'Filing.draft', 'Filing.signed': 'Filing.file' },
     offer: {
       'Filing.order': { price: 25, when: 'accepted', accept: 'customer',
                         acceptWithin: '7d', remedy: 'redo' },
     },
   })
   ```

   A model drafts and a paralegal checks, inside one [Workflow](/workflow.md). The Customer signs, an agent files, and the Customer accepts or rejects what was filed.

3. **Try it locally**

   ```sh title="Terminal"
   $ npx apis.dev dev
   ✓ every Interface on http://localhost:8787, against your Test twin
     reloads on every save
   ```

   Order a filing on localhost and walk it to accepted. In your [Test twin](/reference/platform.md#test-twin) the paralegal's task goes to you, and nothing is filed.

4. **Publish it**

   ```sh title="Terminal"
   $ npx apis.dev deploy
   ✓ signed in with GitHub
   ✓ definition.ts: filings on filings.apis.dev
   ✓ Stripe connected: each Customer pays into your own account
   ✓ 1 Noun, 9 Verbs, 2 Triggers, 1 price
       Filing   order, get, list, update, delete, restore, sign, accept, reject
       Filing.ordered → Filing.draft
       Filing.signed → Filing.file
       Filing.order    25 once, when accepted
   ✓ published v1 to https://filings.apis.dev: 11 Interfaces
       REST              /filings
       CLI               npx filings
       MCP               /mcp
       SDK               npm i filings, pip install filings
       Docs              /docs
       OpenAPI           /openapi.json
       llms.txt          /llms.txt
       agents.json       /.well-known/agents.json
       Pricing           /pricing
       Landing site      /
       Developer portal  /dashboard
   ✓ Test key: filings_test_xZ4w7ptrJjwZ2r8pvwKjvZzH5pqCGj040acc
   ```

   `draft` and `file` serve no path: `Filing.ordered` and `Filing.signed` start them, so they are [internal](/workflow.md#internal). Your Customer makes the other moves.

5. **Read the price list**

   ```json title="/pricing"
   {
     "currency": "USD",
     "rates": [
       { "name": "Filing.order",
         "price": 25, "unit": "outcome",
         "when": "accepted" }
     ]
   }
   ```

   Your Customers and their agents read one price, and when it is charged: on acceptance, never by the hour.

6. **Order one with no credits**

   **curl**

   ```sh title="Request"
   curl https://filings.apis.dev/filings \
     -H "Authorization: Bearer $FILINGS_KEY" \
     -H "Prefer: code=402" \
     --json '{"company":"acme.com","year":2026}'
   ```

   **TypeScript**

   ```ts title="TypeScript"
   import { Filings } from 'filings'

   const api = new Filings({ key: process.env.FILINGS_KEY })
   const { error, meta } = await api.orderFiling({ company: 'acme.com', year: 2026 })
   // error: { status: 402, code: 'payment_required' }
   // meta.cost: { price: 0, currency: 'USD' }
   ```

   **Python**

   ```python title="Python"
   import os
   from filings import Filings

   api = Filings(key=os.environ["FILINGS_KEY"])
   error, meta = api.order_filing(company="acme.com", year=2026)
   # error: { "status": 402, "code": "payment_required" }
   # meta.cost: { "price": 0, "currency": "USD" }
   ```

   **CLI**

   ```sh title="Terminal"
   $ npx filings login --key $FILINGS_KEY
   $ npx filings filings order --company acme.com --year 2026
   error 402 payment_required
   price 0 USD · req_3684ba7186b965f12f33
   ```

   **MCP**

   ```sh title="Request"
   curl https://filings.apis.dev/mcp \
     -H "Authorization: Bearer $FILINGS_KEY" \
     -H "Prefer: code=402" \
     --json '{
     "jsonrpc": "2.0", "id": 1, "method": "tools/call",
     "params": { "name": "orderFiling",
       "arguments": { "company": "acme.com", "year": 2026 } }
   }'
   ```

   ```http title="Response"
   HTTP/1.1 200 OK
   X-Cost: price=0, currency="USD"
   X-Request-Id: req_58df8b1113d14ca2e5ab

   {
     "jsonrpc": "2.0",
     "id": 1,
     "result": {
       "isError": true,
       "content": [
         {
           "type": "text",
           "text": "{\"status\":402,\"code\":\"payment_required\"}"
         }
       ],
       "structuredContent": { "status": 402, "code": "payment_required" },
       "_meta": {
         "cost": { "price": 0, "currency": "USD" },
         "requestId": "req_58df8b1113d14ca2e5ab"
       }
     }
   }
   ```


   ```http title="Response"
   HTTP/1.1 402 Payment Required
   Content-Type: application/problem+json
   X-Cost: price=0, currency="USD"
   X-Request-Id: req_3684ba7186b965f12f33

   {
     "type": "https://filings.apis.dev/docs/errors#payment_required",
     "title": "Payment Required",
     "status": 402,
     "detail": "The credit balance is 0; Filing.order holds 25 until accepted.",
     "code": "payment_required",
     "instance": "req_3684ba7186b965f12f33",
     "offers": [
       {
         "kind": "credits",
         "price": 50,
         "checkout": "https://filings.apis.dev/checkout/credits-50"
       },
       {
         "kind": "credits",
         "price": 200,
         "checkout": "https://filings.apis.dev/checkout/credits-200"
       }
     ]
   }
   ```

   An order holds its price from the credits until the Customer accepts. With none to hold, it answers 402 at price 0, with packs that cover it.

7. **Buy credits and order it**

   **curl**

   ```sh title="Request"
   curl https://filings.apis.dev/filings \
     -H "Authorization: Bearer $FILINGS_KEY" \
     --json '{"company":"acme.com","year":2026}'
   ```

   **TypeScript**

   ```ts title="TypeScript"
   import { Filings } from 'filings'

   const api = new Filings({ key: process.env.FILINGS_KEY })
   const { data, meta } = await api.orderFiling({ company: 'acme.com', year: 2026 })
   // data: {
   //   'id': 'filing_1',
   //   'company': 'acme.com',
   //   'year': 2026,
   //   'status': 'ordered',
   //   'createdAt': '2026-10-24T09:00:00Z',
   //   'updatedAt': '2026-10-24T09:00:00Z'
   // }
   // meta.cost: { price: 0, currency: 'USD' }
   ```

   **Python**

   ```python title="Python"
   import os
   from filings import Filings

   api = Filings(key=os.environ["FILINGS_KEY"])
   data, meta = api.order_filing(company="acme.com", year=2026)
   # data: {
   #   "id": "filing_1",
   #   "company": "acme.com",
   #   "year": 2026,
   #   "status": "ordered",
   #   "createdAt": "2026-10-24T09:00:00Z",
   #   "updatedAt": "2026-10-24T09:00:00Z"
   # }
   # meta.cost: { "price": 0, "currency": "USD" }
   ```

   **CLI**

   ```sh title="Terminal"
   $ npx filings login --key $FILINGS_KEY
   $ npx filings filings order --company acme.com --year 2026
   {
     "id": "filing_1",
     "company": "acme.com",
     "year": 2026,
     "status": "ordered",
     "createdAt": "2026-10-24T09:00:00Z",
     "updatedAt": "2026-10-24T09:00:00Z"
   }
   price 0 USD · req_cbec164c216729631ad5
   ```

   **MCP**

   ```sh title="Request"
   curl https://filings.apis.dev/mcp \
     -H "Authorization: Bearer $FILINGS_KEY" \
     --json '{
     "jsonrpc": "2.0", "id": 1, "method": "tools/call",
     "params": { "name": "orderFiling",
       "arguments": { "company": "acme.com", "year": 2026 } }
   }'
   ```

   ```http title="Response"
   HTTP/1.1 200 OK
   X-Cost: price=0, currency="USD"
   X-Request-Id: req_3c004458951158691c9c

   {
     "jsonrpc": "2.0",
     "id": 1,
     "result": {
       "content": [
         {
           "type": "text",
           "text": "{\"id\":\"filing_1\",\"company\":\"acme.com\",\"year\":2026,\"status\":\"ordered\",\"createdAt\":\"2026-10-24T09:00:00Z\",\"updatedAt\":\"2026-10-24T09:00:00Z\"}"
         }
       ],
       "structuredContent": {
         "id": "filing_1",
         "company": "acme.com",
         "year": 2026,
         "status": "ordered",
         "createdAt": "2026-10-24T09:00:00Z",
         "updatedAt": "2026-10-24T09:00:00Z"
       },
       "_meta": {
         "cost": { "price": 0, "currency": "USD" },
         "requestId": "req_3c004458951158691c9c"
       }
     }
   }
   ```


   ```http title="Response"
   HTTP/1.1 201 Created
   Location: /filings/filing_1
   X-Action-Id: act_1
   X-Cost: price=0, currency="USD"
   X-Request-Id: req_cbec164c216729631ad5

   {
     "id": "filing_1",
     "company": "acme.com",
     "year": 2026,
     "status": "ordered",
     "createdAt": "2026-10-24T09:00:00Z",
     "updatedAt": "2026-10-24T09:00:00Z"
   }
   ```

   After Checkout, the order answers 201 at price 0 and holds $25. `Filing.ordered` starts the draft.

8. **Watch the work, then sign**

   **get**

   ```sh title="Request"
   curl https://filings.apis.dev/filings/filing_1 \
     -H "Authorization: Bearer $FILINGS_KEY"
   ```

   ```http title="Response"
   HTTP/1.1 200 OK
   X-Cost: price=0, currency="USD"
   X-Request-Id: req_f781a6ad9678c1e5146d

   {
     "id": "filing_1",
     "company": "acme.com",
     "year": 2026,
     "status": "drafting",
     "createdAt": "2026-10-24T09:00:00Z",
     "updatedAt": "2026-10-24T09:00:01Z"
   }
   ```

   **sign**

   ```sh title="Request"
   curl -X POST https://filings.apis.dev/filings/filing_1/sign \
     -H "Authorization: Bearer $FILINGS_KEY"
   ```

   ```http title="Response"
   HTTP/1.1 200 OK
   X-Action-Id: act_3
   X-Cost: price=0, currency="USD"
   X-Request-Id: req_47d0a7b035cb42c3265d

   {
     "id": "filing_1",
     "company": "acme.com",
     "year": 2026,
     "status": "signed",
     "createdAt": "2026-10-24T09:00:00Z",
     "updatedAt": "2026-10-25T16:40:00Z"
   }
   ```

   Your Customer watches the filing move: `drafting` while the model and the paralegal work, then `drafted`, their turn. Signing starts the filing.

9. **Accept it**

   **curl**

   ```sh title="Request"
   curl -X POST https://filings.apis.dev/filings/filing_1/accept \
     -H "Authorization: Bearer $FILINGS_KEY"
   ```

   **TypeScript**

   ```ts title="TypeScript"
   import { Filings } from 'filings'

   const api = new Filings({ key: process.env.FILINGS_KEY })
   const { data, meta } = await api.acceptFiling('filing_1')
   // data: {
   //   'id': 'filing_1',
   //   'company': 'acme.com',
   //   'year': 2026,
   //   'status': 'accepted',
   //   'createdAt': '2026-10-24T09:00:00Z',
   //   'updatedAt': '2026-10-26T08:15:00Z'
   // }
   // meta.cost: { price: 25, currency: 'USD' }
   ```

   **Python**

   ```python title="Python"
   import os
   from filings import Filings

   api = Filings(key=os.environ["FILINGS_KEY"])
   data, meta = api.accept_filing("filing_1")
   # data: {
   #   "id": "filing_1",
   #   "company": "acme.com",
   #   "year": 2026,
   #   "status": "accepted",
   #   "createdAt": "2026-10-24T09:00:00Z",
   #   "updatedAt": "2026-10-26T08:15:00Z"
   # }
   # meta.cost: { "price": 25, "currency": "USD" }
   ```

   **CLI**

   ```sh title="Terminal"
   $ npx filings login --key $FILINGS_KEY
   $ npx filings filings accept filing_1
   {
     "id": "filing_1",
     "company": "acme.com",
     "year": 2026,
     "status": "accepted",
     "createdAt": "2026-10-24T09:00:00Z",
     "updatedAt": "2026-10-26T08:15:00Z"
   }
   price 25 USD · req_0978142c439357dbbea3
   ```

   **MCP**

   ```sh title="Request"
   curl https://filings.apis.dev/mcp \
     -H "Authorization: Bearer $FILINGS_KEY" \
     --json '{
     "jsonrpc": "2.0", "id": 1, "method": "tools/call",
     "params": { "name": "acceptFiling", "arguments": { "id": "filing_1" } }
   }'
   ```

   ```http title="Response"
   HTTP/1.1 200 OK
   X-Cost: price=25, currency="USD"
   X-Request-Id: req_223e1f7801aa5d6f104b

   {
     "jsonrpc": "2.0",
     "id": 1,
     "result": {
       "content": [
         {
           "type": "text",
           "text": "{\"id\":\"filing_1\",\"company\":\"acme.com\",\"year\":2026,\"status\":\"accepted\",\"createdAt\":\"2026-10-24T09:00:00Z\",\"updatedAt\":\"2026-10-26T08:15:00Z\"}"
         }
       ],
       "structuredContent": {
         "id": "filing_1",
         "company": "acme.com",
         "year": 2026,
         "status": "accepted",
         "createdAt": "2026-10-24T09:00:00Z",
         "updatedAt": "2026-10-26T08:15:00Z"
       },
       "_meta": {
         "cost": { "price": 25, "currency": "USD" },
         "requestId": "req_223e1f7801aa5d6f104b"
       }
     }
   }
   ```


   ```http title="Response"
   HTTP/1.1 200 OK
   X-Action-Id: act_5
   X-Cost: price=25, currency="USD"
   X-Request-Id: req_0978142c439357dbbea3

   {
     "id": "filing_1",
     "company": "acme.com",
     "year": 2026,
     "status": "accepted",
     "createdAt": "2026-10-24T09:00:00Z",
     "updatedAt": "2026-10-26T08:15:00Z"
   }
   ```


   ```http title="Delivery"
   POST /hooks/filings HTTP/1.1
   Host: hooks.acme.com
   X-Webhook-Attempt: 1
   X-Webhook-Signature: t=1793002500,v1=b8aad23b578b9e3d90ceca7c000bf3e2d6ffe9bf7deaa592147920ed358a0845

   {
     "id": "evt_11",
     "type": "Filing.accepted",
     "at": "2026-10-26T08:15:00Z",
     "seq": 11,
     "data": { "id": "filing_1", "status": "accepted" },
     "charged": { "price": 25, "currency": "USD" }
   }
   ```

   Acceptance is the one move with a price: it charges the held $25 once, and `Filing.accepted` carries the charge to every [Watch](/reference.md#events). A rejected filing is redone at no charge.

10. **Get paid**

   ```text title="Stripe receipt · October 2026 · USD"
   Filing.order     1 × 25       25.00
   ───────────────────────────────────
   Total                         25.00
   Paid from 50.00 of credits    25.00
   Balance                       25.00
   ```

   The accepted filing is one line on your Customer's Stripe receipt, and the money lands in your own Stripe account.

## Result
Your API answers on https://filings.apis.dev: a filing a model drafts, a paralegal checks and the Customer signs, charged $25 once the Customer accepts it.

A rejected filing is redone at no charge. You wrote no case tracker, no hold on the price and no acceptance window.

## Next steps
- **[Start a Content product](/quickstart/content.md) →** sell what you publish, by the read and by the month.
- [Service API](/patterns/service-api.md): outcomes and acceptance, grades, review and supervision.
- [Work](/work.md): the steps agents and people do, reviewed and signed off.