Developers
Reference for the 4 Seasons APIs as they exist in the code today. The agent API lets an assistant act for a homeowner under a mandate the homeowner grants. The partner API lets a business with a key quote, order, track, verify and settle work, and has a sandbox. Both are also available as MCP servers.
OAuth 2.1 with PKCE and dynamic client registration. Each access token is bound to one homeowner mandate.
https://www.build4seasons.com/api/agent/v1https://www.build4seasons.com/api/agent/mcpBearer keys issued by 4 Seasons, with scopes. Live keys run real work; test keys run only in the sandbox.
https://www.build4seasons.com/api/v1https://www.build4seasons.com/api/mcphttps://www.build4seasons.com/spec/work-spec/v1.jsonGenerated from the route table that the API itself enforces and that produces openapi.json. A test key may call a route marked sandbox.
| Area | Routes | Sandbox |
|---|---|---|
| Meta | 2 | 2 |
| Sandbox | 4 | 4 |
| 4 Seasons Certified | 1 | 1 |
| Store and orders | 5 | 2 |
| Capacity | 7 | 0 |
| Work orders | 20 | 15 |
| Work records | 4 | 3 |
| Protected projects | 3 | 0 |
| Work Specs | 6 | 6 |
| Show it | 3 | 0 |
| Permits | 9 | 0 |
| Field data | 5 | 0 |
| Reporting | 10 | 0 |
| Webhooks | 7 | 7 |
| Zapier (CRM) | 18 | 1 |
| Household distribution | 5 | 4 |
| Operators | 2 | 0 |
| Settlement | 14 | 14 |
| Total | 125 | 59 |
Keys are issued by our team; there are no self-serve developer accounts yet. Assistants register themselves through OAuth and act only after a homeowner grants a mandate.
01
Idempotency-Key. The TypeScript uses this helper:const BASE = "https://www.build4seasons.com/api/agent/v1";
async function api(path: string, init: RequestInit = {}) {
const res = await fetch(BASE + path, {
...init,
headers: { Authorization: `Bearer ${token}`, ...init.headers },
});
return res.json();
}Dynamic client registration (RFC 7591). Public clients use PKCE and no secret. The name you register is the one the homeowner sees on the consent screen.
Returns
{ "client_id": "ac_01JC9…", "client_name": "Example Assistant", … }curl -X POST https://www.build4seasons.com/oauth/register \
-H 'Content-Type: application/json' \
-d '{
"client_name": "Example Assistant",
"redirect_uris": ["https://assistant.example.com/oauth/callback"],
"grant_types": ["authorization_code", "refresh_token"],
"token_endpoint_auth_method": "none"
}'Send photos (multipart) or a description, plus one of the mandate's homes. What comes back is a priced Work Spec: scope, acceptance checks, a fixed price or a ceiling, the guarantee and how the money settles. A quote is held for 24 hours.
Returns
{ "spec": "4s.work/v1", "id": "ws_01JD4K7Q2M", "revision": 1,
"price": { "kind": "fixed", "amountCents": 28600, "visitFirst": false, … }, … }curl -X POST https://www.build4seasons.com/api/agent/v1/specs \
-H "Authorization: Bearer $TOKEN" \
-H "Idempotency-Key: 6c1f2a9e-0b7d-4a43-9d0e-2f7c5b1e8a40" \
-F home=hm_7Q2 \
-F intent="Fix this." \
-F photo=@under-the-sink.jpgPick an open window and book the revision you showed the homeowner. Within the mandate the order is created at once and the money is held. Over a limit, the answer is needs_approval with the approval's id, and the booking waits on the homeowner.
Returns
{ "id": "or_01JD4K8B6T", "spec": { "id": "ws_01JD4K7Q2M", "revision": 1 },
"status": "booked", "settlement": { "heldCents": 28600, "releasedCents": 0 }, … }curl https://www.build4seasons.com/api/agent/v1/specs/ws_01JD4K7Q2M/windows \
-H "Authorization: Bearer $TOKEN"
curl -X POST https://www.build4seasons.com/api/agent/v1/specs/ws_01JD4K7Q2M/book \
-H "Authorization: Bearer $TOKEN" \
-H "Idempotency-Key: 0d5e7b8c-4f61-4e0a-a3c2-9b8e1f6d2c17" \
-H 'Content-Type: application/json' \
-d '{ "revision": 1, "window": "win_20261006_1144" }'The order carries its status, the pro's first name and photo, live arrival, every proof and the settlement. The events are the same timeline the webhook sends.
Returns
{ "events": [
{ "type": "settlement.held", "at": "2026-10-06T10:42:10-04:00", "amountCents": 28600 },
{ "type": "proof.checked", "at": "2026-10-06T12:26:03-04:00", "ref": "s1", "result": "verified" },
{ "type": "settlement.released", "at": "2026-10-06T12:26:05-04:00", "ref": "s1", "amountCents": 22100 }, … ] }curl https://www.build4seasons.com/api/agent/v1/orders/or_01JD4K8B6T \
-H "Authorization: Bearer $TOKEN"
curl https://www.build4seasons.com/api/agent/v1/orders/or_01JD4K8B6T/events \
-H "Authorization: Bearer $TOKEN"Accepting releases the money for every milestone whose proof has been checked. It needs the settlement:accept scope. If nobody accepts or disputes, proven work is accepted when the spec's accept window (48 hours) passes.
Returns
{ "id": "or_01JD4K8B6T", "status": "done",
"settlement": { "heldCents": 0, "releasedCents": 28600 } }curl -X POST https://www.build4seasons.com/api/agent/v1/orders/or_01JD4K8B6T/accept \
-H "Authorization: Bearer $TOKEN" \
-H "Idempotency-Key: 7a3e9c1d-2b4f-4d6e-8a0c-5f1b3d7e9a26"02
application/vnd.4s.work-spec+json; version=1. Published as JSON Schema at /spec/work-spec/v1.json. Every amount is integer cents and every time ISO 8601.spec string
4s.work/v1 for this version.id string
ws_…. Stable across revisions of the same spec.revision integer
intent string
site object
homeId string?
address object
line1, city, region, postal, country.access object
notes, contactName, contactPhone. Shown to the assigned pro only.evidence[] array
kind (photo, video, scan), id, capturedAt.scope[] array
ref string
s1), used by price lines and milestones.task string
GET /tasks).qty, unit number, string
location string
params object
trade, license string, string?
requires object
capabilities (what it can do) and credentials (licenses and certifications it holds). See executors below.acceptance[] array
check (in words), proof (photo, video, measurement or telemetry), required.constraints object
needBy (date), windows (morning, midday, afternoon), occupied, pets.price object
currency string
USD. Every amount is integer cents.kind fixed | ceiling
amountCents integer
lines[] array
ref, label, amountCents per scope line.validUntil datetime
confidence number
visitFirst boolean
guarantee object
kind (outcome), days, remedy (redo_or_refund). It holds whoever booked.settlement object
mode string
release_on_proof.milestones[] array
ref, amountCents, releasesOn (accepted). They add up to the price.acceptWindowHours integer
provenance object
clientId, mandateId, createdAt: who asked, under which mandate, when.{
"spec": "4s.work/v1",
"id": "ws_01JD4K7Q2M",
"revision": 1,
"intent": "Fix this.",
"site": {
"homeId": "hm_7Q2",
"address": { "line1": "…", "city": "Atlanta", "region": "GA", "postal": "30306", "country": "US" },
"access": { "notes": "Side door, dog is friendly", "contactName": "…", "contactPhone": "…" }
},
"evidence": [{ "kind": "photo", "id": "ev_8M1", "capturedAt": "2026-10-06T10:41:52-04:00" }],
"scope": [
{
"ref": "s1",
"task": "faucet-supply-line-replace",
"qty": 1, "unit": "each",
"location": "Kitchen sink",
"params": { "size": "3/8 comp x 1/2 FIP", "length": "20in" },
"trade": "plumbing", "license": "ga.plumbing",
"requires": { "capabilities": ["plumbing.fixtures"], "credentials": ["ga.plumbing"] },
"acceptance": [
{ "check": "New braided line connected at the faucet and the valve", "proof": "photo", "required": true },
{ "check": "No drip at either connection with the water on for 2 minutes", "proof": "video", "required": true }
]
},
{
"ref": "s2",
"task": "leak-cleanup",
"qty": 1, "unit": "each",
"location": "Kitchen sink cabinet",
"params": {},
"trade": "plumbing", "license": null,
"requires": { "capabilities": ["plumbing.fixtures"], "credentials": [] },
"acceptance": [
{ "check": "Cabinet floor dry and the old line removed", "proof": "photo", "required": true }
]
}
],
"constraints": { "needBy": "2026-10-06", "windows": ["morning", "midday"], "occupied": true, "pets": true },
"price": {
"currency": "USD",
"kind": "fixed",
"amountCents": 28600,
"lines": [
{ "ref": "s1", "label": "Replace the supply line", "amountCents": 22100 },
{ "ref": "s2", "label": "Leak test and cleanup", "amountCents": 6500 }
],
"validUntil": "2026-10-07T10:42:00-04:00",
"confidence": 0.93,
"visitFirst": false
},
"guarantee": { "kind": "outcome", "days": 365, "remedy": "redo_or_refund" },
"settlement": {
"mode": "release_on_proof",
"milestones": [
{ "ref": "s1", "amountCents": 22100, "releasesOn": "accepted" },
{ "ref": "s2", "amountCents": 6500, "releasesOn": "accepted" }
],
"acceptWindowHours": 48
},
"provenance": { "clientId": "ac_01JC9…", "mandateId": "md_01JC9…", "createdAt": "2026-10-06T10:41:58-04:00" }
}price.kind ceiling the homeowner never pays more; the final is the sum of accepted milestones.visitFirst: true and prices the visit. It can't be booked as a fixed job.03
A line is offered only to executors whose registration covers every entry. capabilities say what it can do; credentials say what it holds, checked by us before it's offered anything.
"requires": {
"capabilities": ["plumbing.fixtures"],
"credentials": ["ga.plumbing"]
}Each proof is checked against its acceptance check and comes back verified, or sent back with the reason. A milestone's money can release only once every required check on its line is verified.
04
{
"id": "md_01JC9…",
"clientId": "ac_01JC9…",
"accountId": "…",
"homes": ["hm_7Q2"],
"scopes": ["specs:write", "orders:write", "orders:read", "changes:decide", "settlement:accept"],
"limits": {
"perOrderCents": 50000,
"monthlyCents": 200000,
"autoApproveUnderCents": 25000,
"categories": ["plumbing", "electrical-minor", "handyman", "appliance"],
"hours": { "earliest": "08:00", "latest": "18:00" }
},
"expiresAt": "2027-10-06T00:00:00Z",
"revokedAt": null
}Every check is recorded with its inputs and answer. An assistant can read its mandate and never change it. Revoking it ends every token at once.
05
https://www.build4seasons.com/api/agent/v1, with Authorization: Bearer an agent access token. Writes need an Idempotency-Key./mandateWhat this assistant may do, and what's left this month/homesThe mandate's homes (id, label, city)/specsPhotos (multipart) or a description, plus a home → a priced Work Spec/specs/:idThe spec at its latest revision (?revision= for an earlier one)/specs/:id/windowsOpen arrival windows/specs/:id/book{ revision, window } → an order, or needs_approval with the approval's id/approvals/:idPending, approved, declined or expired/ordersThis assistant's orders/orders/:idStatus, pro (first name, photo), live arrival, proofs, settlement/orders/:id/eventsThe timeline, as the webhook sends it/orders/:id/changes/:changeId{ decision }, within the mandate or as an approval/orders/:id/acceptAccepts proven work and releases its money/orders/:id/dispute{ reason }: holds the money and opens the guarantee/orders/:id/cancelWithin the free-cancel windowAuthorization server: /.well-known/oauth-authorization-server, /.well-known/oauth-protected-resource, POST /oauth/register, GET /oauth/authorize, POST /oauth/token, POST /oauth/revoke.
06
https://www.build4seasons.com/api/agent/mcpcreate_spec, shows the card, and books with book.For clients configured with a file:
{
"mcpServers": {
"4seasons": { "url": "https://www.build4seasons.com/api/agent/mcp" }
}
}get_mandateGET /mandatelist_homesGET /homescreate_specPOST /specsget_specGET /specs/:idlist_windowsGET /specs/:id/windowsbookPOST /specs/:id/bookget_approvalGET /approvals/:idlist_ordersGET /ordersget_orderGET /orders/:iddecide_changePOST /orders/:id/changes/:changeIdacceptPOST /orders/:id/acceptdisputePOST /orders/:id/disputecancelPOST /orders/:id/cancel07
{ error: { code, message } }. The message is for people; branch on the code. Beside each code is its HTTP status.unauthorized401No token, an expired one, or one whose mandate was revoked. Refresh, or send the homeowner back through authorize.mandate_denied403Outside the mandate's scopes, homes, categories or hours. It never becomes an approval; don't retry it.needs_approval202Over a limit. An Approval was created and pushed to the homeowner, and the action waits on it. The body carries the approval's id; follow GET /approvals/:id.over_limit429Ten approvals are already waiting on the homeowner. Wait for them to decide before asking again.not_found404No such thing, or not one this mandate can see. Another account's ids look the same as ids that don't exist.invalid_request400The body or a parameter is wrong; the message says which.idempotency_key_required400A write came without an Idempotency-Key.idempotency_conflict409The key was used before with a different body. Make a new key for a new action.quote_expired409The spec's price is past validUntil. Fetch the spec again for a new revision and book that.visit_first409The read wasn't confident enough to fix a price. Book the visit the spec prices instead. (422 when the photos show nothing usable.)out_of_area422We don't work at that address yet.window_unavailable409The window was taken. List windows again and pick another.invalid_state409The order can't do that now, such as cancelling after the free-cancel window or accepting work that isn't proven.payment_method_required409The homeowner has no saved method to hold the price on. They add one in the Home app.payment_declined402The hold or charge was declined. Tell the homeowner.rate_limited429Too many calls. Back off and retry with the same key.08
09
settlement.heldThe price was held on the homeowner's saved method at booking.proof.recordedA proof for an acceptance check was captured (ref, check, kind).proof.checkedThe proof was checked against its acceptance check: verified, or sent back with the reason.settlement.releasedA milestone's money was released, by the assistant, the homeowner or the accept window.settlement.frozenA dispute holds the milestone and opens the guarantee.settlement.refundedOps settled a dispute with a refund, in part or whole.settlement.collectedReleased money was taken from the homeowner's method (captured from the hold, or charged).settlement.voidedNothing was released, so the hold was let go.Each is an event on the order (GET /orders/:id/events, and the webhook) and an entry in the ledger.
{
"id": "ev_01JD4M…",
"type": "settlement.released",
"at": "2026-10-06T12:26:05-04:00",
"orderId": "or_01JD4K8B6T",
"mandateId": "md_01JC9…",
"ref": "s1",
"amountCents": 22100,
"settlement": { "heldCents": 6500, "releasedCents": 22100 }
}10
https://www.build4seasons.com/api/v1, with Authorization: Bearer a key we issue: 4s_live_… for real work, 4s_test_… for the sandbox. Each key carries scopes, and every route checks one. The whole API is described in OpenAPI 3.1, generated from the same route table that enforces scopes and the sandbox.https://www.build4seasons.com/api/v1/openapi.jsonhttps://www.build4seasons.com/api/mcphttps://www.build4seasons.com/api/v1/sandboxcompile_spec, quote_work_orderPOST /specs/compile, POST /work-orders/quotequote_work_orderPOST /work-orders/quotecommit_work_orderPOST /work-orders/{id}/commitget_work_order, list_work_ordersGET /work-orders/{id}add_work_order_evidence, list_work_order_verdictsPOST /work-orders/{id}/proof, GET /work-orders/{id}/verdictsaccept_work_order, dispute_work_order, list_invoicesPOST /work-orders/{id}/accept, GET /invoicesget_work_record, verify_work_recordGET /work-records/{id}, POST /work-records/verifyIdempotency-Key; a retry with the same key and body gets the first answer.x-ratelimit-* headers; a 429 says when to retry.x-request-id and 4s-livemode. We keep the method, path, status and latency of each request for 30 days, never bodies or keys.11
After a commit, the simulated crew moves a step every 3 seconds: offered, accepted, on the way, arrived, each proof shot recorded and checked, a verdict per task, proven. Proven work you don't accept or dispute is accepted after 60 seconds, then it settles, is invoiced and paid. The same scenario always gives the same events in the same order. To skip the wait, call POST /sandbox/work-orders/{id}/advance with { "to": "next" } or { "to": "end" }, or the MCP tool sandbox_advance.
Send X-4S-Simulate on a quote or commit (or simulate to the MCP tools).
happyA crew accepts, arrives on time, proves every shot, and the work is verified the first timefail_first_passThe first after photo fails its first-pass check; the crew retakes it and it passesfail_verificationVerification rejects the first task; the crew reworks it, proves it again and it is acceptedcrew_declinesThe first crew offered declines; the second acceptslate_arrivalThe crew is delayed on the way and sends a new arrival timechange_over_limitThe crew finds work over your pre-approved change limit; nothing goes on until you approve or decline itno_crewNo crew accepts the visit; the work order is cancelled and nothing is chargedout_of_areaThe quote is refused as out of area (422 out_of_area)no_windowsThe quote comes back with no open windows, so a commit is refused (409 window_unavailable)# Price two tasks with a test key. Nothing real happens in the sandbox.
curl https://www.build4seasons.com/api/v1/work-orders/quote \
-H "Authorization: Bearer 4s_test_…" \
-H "Idempotency-Key: quote-unit-4b-1" \
-H "Content-Type: application/json" \
-d '{ "tasks": [{ "task": "supply-lines" }, { "task": "gfci" }],
"site": { "address": "9 Oak St NE", "zip": "30307",
"contact": { "name": "Dana Park", "phone": "404-555-0170" } } }'
# Book the first option, and make the first proof photo fail its check.
curl https://www.build4seasons.com/api/v1/work-orders/WO-T1001/commit \
-H "Authorization: Bearer 4s_test_…" \
-H "Idempotency-Key: commit-unit-4b-1" \
-H "X-4S-Simulate: fail_first_pass" \
-H "Content-Type: application/json" \
-d '{ "date": "2026-10-09", "window": "10" }'
# Run the simulated crew to the end: travel, proof, verdicts, settlement.
curl https://www.build4seasons.com/api/v1/sandbox/work-orders/WO-T1001/advance \
-H "Authorization: Bearer 4s_test_…" \
-H "Content-Type: application/json" \
-d '{ "to": "end" }'12
POST /api/v1/webhooks; its signing secret is returned once. Each event is signed, retried and logged, and you can send any of them again.4s-signature is t=<unix>,v1=<hex>: an HMAC-SHA256 of t.body with your secret. Reject a timestamp more than five minutes old and compare in constant time.4s-webhook-id is the event id, the same on every retry and replay. Keep the first and drop the rest.GET /webhooks/deliveries lists each delivery with its status, attempts and latency; POST /webhooks/deliveries/{id}/replay sends it again.POST /webhooks/{id}/rotate-secret issues a new secret. For the grace period (24 hours unless you say otherwise) each delivery carries a second v1 signed with the old one, so you can switch without missing an event.import { createHmac, timingSafeEqual } from "node:crypto";
// header: the 4s-signature request header. body: the raw request body, as received.
export function verify4s(secret: string, body: string, header: string, toleranceSec = 300): boolean {
const parts = header.split(",").map((p) => p.split("=") as [string, string]);
const t = Number(parts.find(([k]) => k === "t")?.[1]);
if (!Number.isFinite(t) || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;
const expected = createHmac("sha256", secret).update(`${t}.${body}`).digest();
// During a secret rotation there are two v1 values: either one is enough.
return parts.some(([k, v]) => k === "v1" && v.length === 64 && timingSafeEqual(Buffer.from(v, "hex"), expected));
}Settlement API · v1
For warranty firms, property-management software, insurers and roll-ups. You send the job as a Work Spec and proof for each step. Each step is checked to the same standard as our own work, its pay is released to your contractor once it is verified, and you receive a signed Work Record and the lien waivers.
Money moves only on a verified step. Each step is judged on the standard of its step kind (task.leak-repair, inspection.final-building; an unknown kind is held to the generic standard). Where the Oracle can't decide on its own, a person at 4 Seasons does. A rejected step pays nothing until new proof is verified.
Pay goes over two rails. Each executor's rail is Stripe Connect or a Whop Pro account, with the other as the backup. If a rail is down or paused, the release goes over the other. A rail that may already have sent a payment keeps it until it answers, so a release is never paid twice.
From what you funded. Releases draw on your funded balance, the step's pay plus the fee. A release your balance can't cover waits, with the reason, and goes out when funding lands.
A thin fee on settled volume. A share of each released step, in basis points (GET /settlement says the current rate). Never per seat, per key or per lead.
Your data is yours. A key sees only its tenant's jobs, executors, releases and waivers; anything else answers 404 not_found.
All under /api/v1 with Authorization: Bearer 4s_live_… (or 4s_test_…). Writes take an Idempotency-Key. Amounts are integer cents. Errors are { error: { code, message } }. The same operations are MCP tools at /api/mcp, and every route is in openapi.json.
| GET | /settlement | Your account: brand, fee in basis points, whether live money moves, funded balance |
| GET | /settlement/executors | Your executors and whether each one's pay account is ready on each rail |
| POST | /settlement/executors | Add a contractor by your own id for them (same id, same executor) |
| POST | /settlement/executors/{id}/onboarding | Hosted page where they open their pay account: Stripe Connect or Whop |
| POST | /settlement/jobs | A job from a Work Spec: site, owner, your reference, steps with pay |
| GET | /settlement/jobs?status=&reference= | Your jobs, newest first |
| GET | /settlement/jobs/{id} | Steps, proof plans, verdicts, releases (id, SJ-number or your reference) |
| POST | /settlement/jobs/{id}/executors | Give steps to an executor |
| POST | /settlement/jobs/{id}/steps/{step}/proof | Proof for a step; the Oracle judges it; pay goes only once it's verified |
| GET | /settlement/jobs/{id}/record | The signed Verified Work Record |
| GET | /settlement/jobs/{id}/lien-waivers | The statutory waiver for each release |
| POST | /settlement/lien-waivers/{id}/sign | The executor's typed signature, from your app |
| GET | /settlement/releases?status=&job= | Pay released on verified steps: amount, fee, rail, held reason |
| GET | /settlement/statements/{YYYY-MM} | A month: funding in, settled by rail, the fee, the balance |
# 1. Your contractor, and where they open their pay account
curl -X POST https://www.build4seasons.com/api/v1/settlement/executors \
-H "authorization: Bearer $KEY" -H "idempotency-key: tech-77" \
-H "content-type: application/json" \
-d '{"externalRef":"tech-77","name":"Leo Plumbing","email":"leo@example.com"}'
curl -X POST https://www.build4seasons.com/api/v1/settlement/executors/$EXECUTOR/onboarding \
-H "authorization: Bearer $KEY" -H "idempotency-key: tech-77-onboard" \
-H "content-type: application/json" \
-d '{"rail":"stripe","returnUrl":"https://you.example/back","refreshUrl":"https://you.example/again"}'
# 2. A job from a Work Spec
curl -X POST https://www.build4seasons.com/api/v1/settlement/jobs \
-H "authorization: Bearer $KEY" -H "idempotency-key: claim-881" \
-H "content-type: application/json" \
-d '{"title":"Leak under the kitchen sink","reference":"CLAIM-881",
"site":{"address":"48 Ponce De Leon Ave NE","city":"Atlanta","county":"Fulton","state":"GA","zip":"30308"},
"owner":{"name":"Maria Lopez"},
"workSpec":{"steps":[{"key":"fix","name":"Find and repair the leak","kind":"task.leak-repair","payoutCents":20000}]},
"executor":"'$EXECUTOR'"}'
# 3. Proof for the step. Its pay is released once the step is verified.
curl -X POST https://www.build4seasons.com/api/v1/settlement/jobs/CLAIM-881/steps/fix/proof \
-H "authorization: Bearer $KEY" -H "idempotency-key: claim-881-fix-1" \
-H "content-type: application/json" \
-d '{"photos":[{"shot":"after","sha256":"<hex sha-256 of the file>","capturedAt":"2026-10-13T15:02:00Z","lat":33.77,"lng":-84.38}]}'A test key (4s_test_…) runs the same flow on its own tenant line. Executors come with a verified pay account, a simulated reviewer decides at once, and releases go over a simulated rail. Nothing reaches a real rail, real money or our ledger. Every answer says livemode: false.
On a proof, X-4S-Simulate forces an outcome: happy (the default), fail_verification (the first round is sent back), review (left for a person, as live edge cases are).
Register endpoints with POST /api/v1/webhooks, signed 4s-signature: t=…,v1=… and retried like the rest of the API (signing). Settlement events:
GET /settlement/jobs/{id}/record returns the signed Verified Work Record: each step's proof hashes, the Oracle's verdict and rule version, who did it, and whether its pay was released and paid. Anyone can check it at POST /api/v1/work-records/verify.
For a job in Georgia, each release files the statutory waiver (O.C.G.A. 44-14-366): interim, and final on an executor's last step. Your app collects the executor's typed signature and sends it to /settlement/lien-waivers/{id}/sign. Other states' forms aren't built yet.
The Work Protocol
How an assistant orders a repair for a homeowner through the Work Protocol, within limits the homeowner sets. The film below is a scripted demonstration with a made-up home and pro; the objects, limits and rules it shows are the protocol's.
Sample job · scripted · nothing is booked or chargedSample job · nothing is charged
Chapter 1 of 9, Ask. A homeowner takes one photo under the kitchen sink and sends it to their assistant with two words: Fix this.
Ask
Read
Quote
Booked
On the way
Work
Paid
Home file
Over a limit
01/09 Ask0:00 / 1:17
The problem
An assistant can book a flight, pay a bill and file a return through an API. A leak under the sink still takes phone calls, a visit to get a price and payment before anyone can show what was done. There has been nothing for software to call, so the assistant could only tell you the faucet was leaking.
The protocol
Each is an object in the API with an id, a state and a history. Together they let a request become finished, paid work without a phone call.
A versioned description of one job: site, scope, acceptance checks, price, guarantee and settlement.
The homeowner's standing permission for one assistant: what it may do, how much, where and until when.
A photo, a video, a measurement or a reading for each acceptance check, recorded in the work ledger.
The money is held at booking and released one milestone at a time, as each one's proof is accepted.
A request to the homeowner when an action is over the mandate's limits. Nothing over a limit happens without one.
The network
People do the work today. A humanoid robot, an inspection drone or a robotic painting system can register as another executor and take the Work Specs it can satisfy, on the same specs, mandates, proof and settlement.
Assistants, insurers, property systems, manufacturers, enterprises
Send what needs doing, with the evidence, under a mandate.
e.g. Your assistant sends a photo of a leak.
Specification, pricing, orchestration, trust and settlement
Turns the request into a Work Spec with a price, finds who can do it, and holds the money until it's proven.
e.g. Prices the repair at $286 and holds it.
Contractors today, then fleets and specialized operators
Take the Work Specs their credentials and capabilities satisfy, and prove each acceptance check.
e.g. Marcus, a licensed plumber, fixes it.
Robots and autonomous systems
Register as executors for the specs they can satisfy, with the same proof and settlement.
e.g. An inspection drone checks a roof.
Trust
Only the homeowner sets them, in the Home app or on the website. Revoking a mandate ends every token it issued at once.
The money is held at booking. Each milestone is released when its proof is accepted, by the assistant, the homeowner, or the 48-hour window passing. A dispute freezes it and opens the guarantee.
Each request is checked against the mandate and recorded with the assistant, the mandate, the inputs and the answer. Every order says which assistant booked it.
The homes on its mandate and the pro's first name and photo. Never a pro's phone number, and never the homeowner's card.
For homeowners
It takes a minute, and nothing is booked until your assistant asks for something and it fits your limits or you approve it.
https://www.build4seasons.com/api/agent/mcp