{
  "contract": "book",
  "name": "Book — the pro published a price, so book it",
  "status": "preview",
  "statusLabel": "PREVIEW",
  "statusNote": "Preview — shapes may change before GA. Fees: TBD. Not callable yet — request access: https://trycenter.com/developers#early-access",
  "summary": "Book a listed price in one round-trip. POST → 402 Payment Required (fee envelope) → pay → retry with the same idempotencyKey → 201 + signed receipt. held → confirmed flips on the principal’s own confirmation; unconfirmed holds expire and auto-refund.",
  "bookability": "Bookable iff the pro opted in, the service carries a real published price, and the slot is genuinely open. That published price IS the test — it is why this contract is called Book. Anything failing a leg is absent from agent-facing listings: never padded, never \"call to check\", never an invented price.",
  "priceList": "The collection of a pro’s published prices is that pro’s price list, readable today in their agent card at /{handle}/agent.json.",
  "endpoint": {
    "method": "POST",
    "path": "/v1/agent/bookings",
    "auth": "Bearer <agent_key>"
  },
  "flow": [
    "POST /v1/agent/bookings → 402 Payment Required with the fee envelope (prepaid balance / invoice reference; x402 is SPECCED, not live)",
    "Pay, then retry with the SAME idempotencyKey + payment proof",
    "201 with the booking, its hold expiry, and a signed receipt (JWS) — receipts are SPECCED",
    "Principal confirms on their own phone → status flips held → confirmed (webhook / status link)"
  ],
  "idempotency": "idempotencyKey is required and immortal per agent account. Replay returns the same body — same bookingId, same receipt — never a second booking or charge. Same key with a different payload → 422 idempotency_conflict.",
  "fees": "TBD — no fee schedule is signed. feeMinor is null in this example on purpose.",
  "settlement": "Fees are captured at purchase but earned only at completion. no_show / cancelled / expired → auto-refund to the same instrument that paid. (SPECCED.)",
  "statuses": [
    "held",
    "confirmed",
    "completed",
    "cancelled",
    "no_show",
    "refunded",
    "expired"
  ],
  "request": {
    "idempotencyKey": "agt_9f2c1e07",
    "businessId": "biz_7d31",
    "serviceId": "svc_a410",
    "durationMin": 90,
    "startAt": "2026-09-01T17:00:00Z",
    "principal": {
      "name": "Alex",
      "phone": "+16025550111",
      "attestPrincipalControl": true
    },
    "notes": "gate code 4411"
  },
  "responses": {
    "201": {
      "bookingId": "bk_5c22",
      "status": "held",
      "startAt": "2026-09-01T17:00:00Z",
      "endAt": "2026-09-01T18:30:00Z",
      "service": {
        "name": "Water heater replacement",
        "durationMin": 90,
        "priceMinor": 22000
      },
      "business": {
        "name": "Desert Plumbing",
        "areaLabel": "Scottsdale"
      },
      "feeMinor": null,
      "priceMinor": 22000,
      "confirmation": {
        "pending": "principal_confirm",
        "holdExpiresAt": "2026-09-01T17:30:00Z"
      },
      "receipt": {
        "jws": "eyJhbGciOiJFUzI1NiIs"
      },
      "links": {
        "status": "/v1/agent/bookings/bk_5c22"
      }
    }
  },
  "errors": {
    "400": "attestPrincipalControl !== true, or a duration the pro does not list",
    "402": "payment required — fee envelope in the body",
    "409": "slot_taken — the slot filled between read and commit; a paid-but-stale retry auto-refunds",
    "422": "idempotency_conflict — key reused with a different payload"
  },
  "relatedContract": {
    "contract": "quote",
    "url": "https://trycenter.com/developers/contracts/quote.json",
    "note": "An accepted bid is a listed price of one — for that buyer, valid until it expires."
  },
  "docs": "https://trycenter.com/developers",
  "htmlTwin": "https://trycenter.com/developers",
  "markdownTwin": "https://trycenter.com/developers.md"
}