Deploy
A deploy builds your folder into a version, checks it against live, serves it in test mode and runs your brand's own suite against it. deploy --live sends that exact version to buyers, all at once or in stages that compare it job for job against live and abort on their own, and every job already running keeps the version it started on.
A deploy answers one question: is this exact version ready for a buyer's money? Every version passes through these states, and only the last two take real orders.
| State | What it is | Who reaches it |
|---|---|---|
draft | your folder, built into a version: a name, 2026-11-02.1, and the sha256 of the whole bundle | no one yet: check runs, and check --against live diffs it |
tested | the draft, served in test mode at your real address, with your suite run against it | every sk_test_ key, your staff and the simulated market |
staged | the tested version, taking a share of new orders beside live | the buyers whose orders fall in its share |
live | the tested version, promoted by its sha256 | every buyer, member and agent, on live keys |
deploy takes the folder to tested. A version that fails check or a suite case stays a draft, and the run names the case:
npx services.dev deployapi.lawyer · 9 files · 6 Services, 1 Pool, rules 2026-10 and the brand
version 2026-11-02.1 · sha256:4e1b77c09a3f5d21… · draft
check 0 failed · against live 2026-10-19.3: 1 additive, 0 breaking
test https://api.lawyer in test mode · every sk_test_ key reaches 2026-11-02.1 now
suite 268 passed · 0 failed · 83 live rows wait for deploy --live
✓ tested 2026-11-02.1 · live is still 2026-10-19.3 · npx services.dev deploy --live sends itThe suite is the one your brand publishes, so the test a buyer could run against you is the test your deploy runs first. Test and live covers what test mode simulates and where each mode serves.
deploy --live promotes the tested version. It never rebuilds: the sha256 that passed is the sha256 that serves.
npx services.dev deploy --liveapi.lawyer · 2026-11-02.1 · sha256:4e1b77c09a3f5d21… · tested 14 minutes ago
plan Team (services.dev) · your plan · every gated key covered
stripe acct_1Q8Lw4 · charges on · payouts on
model your model key · 9 AI steps, 8 pinned to a build
warns trademark-response.draft names only the family claude-x: its build can move under you · pin one, claude-x@<date>
domain api.lawyer · verified · certificate renews 2027-01-30
suite 83 live rows, read-only: auth, audience and leak cases · 0 failed · nothing ordered, nothing charged
✓ live https://api.lawyer · api, mcp, cli, sdk, site, docs and every app
pinned 41 matters in flight stay on 2026-10-19.3 until they end · new orders take 2026-11-02.1
event version.live · 2026-10-19.3 retires when its last matter, clock and timer closeFive things are checked first, and each refusal names its cure:
| Checked | Refused with | Cure |
|---|---|---|
| each Service's plan, derived from its keys | OFFER, with plan, because and cure | upgrade, or check --fit for the rewrite that stays put: going live on a gated key |
| no breaking change under pinned jobs or keys | breaking_change | Versions |
| Stripe connected, with charges and payouts on | stripe_unconnected | services.dev connect stripe |
| a model key for live, and a model on every AI step | model_unset | services.dev model set |
Brand.domain verified, on Launch and up | domain_unverified | the two DNS records services.dev domains prints |
The live suite rows then run read-only, so the auth, audience and leak cases test live keys without ordering anything. A rename under jobs that still have the step ahead is refused:
npx services.dev deploy --live # the same tree, with a step renamed✗ breaking_change · services/trademark-response.ts:18 · draft → write
pinned 37 matters on 2026-11-02.1 still have draft ahead of them
cure keep the step's name, ship it under a new Service id, or deploy --live --breaking: pinned matters keep their version, and keys pinned to it keep its shapes
docs https://services.dev/docs/operate/versions#what-counts-as-breakingA step's model pins a family or a build. model: 'claude-x' pins the family, and the provider may move its build under you. model: 'claude-x@2026-11-08' pins the build, so a new build reaches buyers only through a deploy you make. deploy --live warns on every reviewed step pinned only to a family, as the run above does for draft. The incident is what a family pin allows.
--stages sends a share of new orders to the new version and leaves the rest on live. Every job keeps its version for life, so the console compares the two cohorts job for job: send-back rate, gate refusals, AI a job and time to each step.
npx services.dev deploy --live --stages 25%,100% --hold 7d # 16 Nov: the draft's brief argues a prior registrationapi.lawyer · 2026-11-16.1 · sha256:b83e0f5a72c1d946… · tested 9 minutes ago
checks plan, stripe, model, domain and the live suite, as deploy --live · 0 failed
staged 25% of new orders take 2026-11-16.1 for 7 days · the rest take live 2026-11-09.1 · version.staged
judged the two cohorts, job for job: send-back rate, gate refusals, AI a job, time to each step
aborts on its own, the first alarm on the new cohort: new orders go back to 2026-11-09.1 · version.abortedA stage promotes when its window passes, its cohort holds at least 10 jobs and no alarm fires on it. On a breach it aborts on its own: new orders go back to live, the staged jobs finish on their version, and version.aborted names the measure that fell. The cohorts are judged the way evals judge a change before it ships and holdouts judge a line after it moves: three readings of one record. Stages run on Team.
open https://api.lawyer/console/deploys # 23 Nov, 14:00Deploys · live 2026-11-09.1 · staged 2026-11-16.1 at 25%, day 7 of 7
cohort trademark responses · 17 on 2026-11-16.1 · 58 on 2026-11-09.1
send-backs 0 of 17 (0%) · 2 of 58 (3%)
refusals 0 · 0
AI a job $0.74 · $0.71
to respond p50 4h 10m · 4h 25m
✓ promoted 2026-11-16.1 to 100% · no alarm on its cohort, 17 jobs over the 10 a stage needs · version.promotedapis.tax ships nothing in the week before the filing date. Brand.freeze names the window and what still runs inside it:
freeze: [{ from: '2027-04-08', to: '2027-04-16', allow: ['hold', 'rollback', 'respond'] }],npx services.dev deploy --live # apis.tax, 12 Apr, inside its freeze✗ frozen · brand.ts:14 · Brand.freeze holds live deploys from 2027-04-08 to 2027-04-16
runs hold, rollback and respond, as the freeze allows
cure deploy after 2027-04-16, or deploy --live --break-freeze --reason "<why>": written to the audit log--break-freeze --reason ships anyway, and the reason goes on the audit log.
open https://api.lawyer/console/deploysDeploys · live 2026-11-02.1 since 2 Nov 15:12 · Team
2026-11-02.1 live sha256:4e1b77c0 suite 268 + 83 live · 1 additive · 212 matters since
2026-10-19.3 pinned sha256:9c02d1aa 9 matters · 18 clocks · 63 timers · retires when they close
2026-10-19.2 retired sha256:51f0b6e2 nothing pinned since 30 Oct
actions diff against live · roll back to 2026-10-19.3 · repin its 9 mattersThe first live deploy, start to finish, with Stripe connected and the first payout: take the first real job.
| Call (CLI) | API | Event | On the console | Plan |
|---|---|---|---|---|
deploy | POST https://services.dev/v1/deploys | version.deployed, version.tested, version.failed | /console/deploys | every plan |
deploy --live [--breaking] | POST https://services.dev/v1/deploys/:version/live | version.live | /console/deploys | every plan |
deploy --live --stages <shares> --hold <span> | POST https://services.dev/v1/deploys/:version/live { stages, hold } | version.staged, version.promoted, version.aborted | /console/deploys | Team |
deploy --live --break-freeze --reason <why> | POST https://services.dev/v1/deploys/:version/live { break_freeze } | version.live | /console/audit | Team |
connect stripe | POST https://services.dev/v1/stripe/connect | stripe.connected | /console/keys | every plan |
model set <key> | PUT https://services.dev/v1/model | model.set | /console/keys | every plan |
domains | GET https://services.dev/v1/domains | domain.verified | /console/keys | Launch |