{"openapi":"3.0.3","info":{"title":"AgentBookable — Booking API for AI agents","version":"3.0.0","description":"One booking interface for AI agents. Businesses keep their existing booking system and connect through AgentBookable (Composio-backed connectors such as Cal.com). Provider-specific identifiers are never exposed.\n\n## Workflow\n\n1. Discover businesses: `GET /businesses`.\n2. Read capabilities on the business record.\n3. List services: `GET /businesses/{businessId}/services`.\n4. Check live availability: `POST /businesses/{businessId}/availability`.\n5. Prepare a draft: `POST /booking-drafts`.\n6. Obtain explicit user consent to that draft.\n7. Authorize: `POST /mandates/for-draft`.\n8. Book: `POST /bookings` with mandateRef + draftId.\n\nExample published business: `agentbookable-demo`.","contact":{"name":"AgentBookable","url":"https://agentbookable.com"}},"servers":[{"url":"https://agentbookable.com"}],"tags":[{"name":"discovery","description":"Find businesses and read their capabilities"},{"name":"services","description":"List bookable services and their requirements"},{"name":"availability","description":"Live slot lookup"},{"name":"mandates","description":"User-authorization records for consequential actions"},{"name":"bookings","description":"Create and inspect bookings"}],"paths":{"/businesses":{"get":{"tags":["discovery"],"summary":"Search published businesses","description":"Search the AgentBookable registry. Falls back to legacy seeded businesses when the registry has no matches. Returns capability flags for each business.","operationId":"searchBusinesses","parameters":[{"name":"query","in":"query","schema":{"type":"string"},"description":"Free-text match on name/category/description/services","example":"hairdresser"},{"name":"category","in":"query","schema":{"type":"string"},"description":"Exact category match","example":"hairdresser"},{"name":"city","in":"query","schema":{"type":"string"},"example":"London"},{"name":"country","in":"query","schema":{"type":"string"}},{"name":"service","in":"query","schema":{"type":"string"},"description":"Match service names","example":"Haircut"},{"name":"latitude","in":"query","schema":{"type":"number"}},{"name":"longitude","in":"query","schema":{"type":"number"}},{"name":"radiusKm","in":"query","schema":{"type":"number","default":25}}],"responses":{"200":{"description":"Matching published businesses","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Business"}},"example":[{"id":"ab_bus_c24850fce8cfb9db","name":"AgentBookable Demo","slug":"agentbookable-demo","category":"hairdresser","location":{"city":"London","country":"FR"},"timezone":"Europe/Paris","provider":{"type":"registry"},"capabilities":{"services":true,"availability":true,"booking":true,"reschedule":false,"cancellation":false,"payment":false,"instantConfirmation":true}}]}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/businesses/{businessId}":{"get":{"tags":["discovery"],"summary":"Get a business record","description":"Normalized details + capability flags. Accepts an `ab_bus_*` id or a public slug (e.g. `agentbookable-demo`). Always read `capabilities` before attempting actions.","operationId":"getBusiness","parameters":[{"name":"businessId","in":"path","required":true,"schema":{"type":"string"},"example":"agentbookable-demo"}],"responses":{"200":{"description":"The business record","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Business"}}}},"404":{"description":"Business not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"BUSINESS_NOT_FOUND","message":"Business 'x' not found in the registry."}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/businesses/{businessId}/services":{"get":{"tags":["services"],"summary":"List bookable services","description":"Services the business has opened to AI booking. Each service carries `bookingRequirements` — check them before booking (e.g. a required customer phone) so you can collect the details from the user up front.","operationId":"listServices","parameters":[{"name":"businessId","in":"path","required":true,"schema":{"type":"string"},"example":"agentbookable-demo"}],"responses":{"200":{"description":"Bookable services","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Service"}}}}},"404":{"description":"Business not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/businesses/{businessId}/availability":{"post":{"tags":["availability"],"summary":"Find live availability","description":"Live slots for a service. Slots are TEMPORARY (~60 seconds, see `expiresAt`) and revalidated at booking time. Always call this immediately before `POST /bookings`. If the chosen slot is taken you receive `409 SLOT_NO_LONGER_AVAILABLE` with alternatives.","operationId":"findAvailability","parameters":[{"name":"businessId","in":"path","required":true,"schema":{"type":"string"},"example":"ab_bus_c24850fce8cfb9db"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["serviceId","from","to"],"properties":{"serviceId":{"type":"string","example":"bb_srv_e64d99e1e1243d68"},"from":{"type":"string","description":"ISO-8601 window start","example":"2026-09-01T00:00:00Z"},"to":{"type":"string","description":"ISO-8601 window end","example":"2026-09-01T23:59:59Z"}}}}}},"responses":{"200":{"description":"Live slots in the window","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AvailabilityResult"},"example":{"businessId":"ab_bus_c24850fce8cfb9db","serviceId":"bb_srv_e64d99e1e1243d68","slots":[{"id":"bb_slot_8d05aa3a","serviceId":"bb_srv_e64d99e1e1243d68","start":"2026-09-01T09:00:00+02:00","end":"2026-09-01T09:30:00+02:00","staffId":"sb-unit-2","fetchedAt":"2026-08-29T14:30:39Z","expiresAt":"2026-08-29T14:31:39Z"}]}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"Business or service unknown","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/booking-drafts":{"post":{"tags":["mandates"],"summary":"Prepare a booking draft (the proposal shown to the user)","description":"Creates an immutable booking proposal with an opaque draft id. Validates live availability (the slot must be genuinely free now) and the service's customer requirements. Show this draft to the user as the final proposal before asking for consent.","operationId":"createBookingDraft","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DraftCreate"}}}},"responses":{"201":{"description":"Draft created — show it to the user and obtain consent","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingDraft"}}}},"409":{"description":"Slot no longer available","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"SLOT_NO_LONGER_AVAILABLE","message":"That appointment is no longer available.","alternatives":[{"start":"2026-09-02T09:30:00+02:00","end":"2026-09-02T10:00:00+02:00"}]}}}},"422":{"description":"Missing required customer details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"MISSING_CUSTOMER_REQUIREMENT","message":"The business requires additional customer information before booking: phone. Ask the user for phone first."}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/mandates":{"post":{"tags":["mandates"],"summary":"Record the user's booking instruction (authorization)","description":"Creates a short-lived, single-use mandate that authorizes ONE booking action for ONE business. The instruction must be the user's own words — agents must not fabricate it. Use the returned `mandateRef` in `POST /bookings` within 15 minutes. The mandate is consumed by the booking it authorizes.","operationId":"createMandate","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MandateCreate"},"example":{"actorRef":"assistant-claude-session-42","action":"create_booking","businessId":"ab_bus_c24850fce8cfb9db","instruction":"Book the 09:00 haircut on 1 September."}}}},"responses":{"200":{"description":"Mandate created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MandateCreated"},"example":{"mandateRef":"mandate_76a00b335de7bd57","expiresAt":"2026-08-29T14:45:00.000Z"}}}},"400":{"description":"Missing/invalid fields","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/mandates/for-draft":{"post":{"tags":["mandates"],"summary":"Issue a consent-bound mandate for a draft","description":"Assert that the user explicitly consented to the exact draft, and receive a single-use mandate bound to it. The consent assertion is audited (actorRef, method, draft hash). Any later change to the consequential booking details invalidates this authorization.","operationId":"createMandateForDraft","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConsentCreate"},"example":{"actorRef":"assistant-session-42","draftId":"draft_abc123","consent":{"consentObtained":true,"draftId":"draft_abc123","consentedBy":"the customer","consentMethod":"chat reply: yes, book it","instruction":"Yes, book it"}}}}},"responses":{"200":{"description":"Mandate issued","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MandateCreated"}}}},"410":{"description":"Draft expired — prepare a new draft","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"DRAFT_EXPIRED","message":"The booking draft has expired. Re-check availability, prepare a new draft, and obtain fresh consent."}}}},"422":{"description":"CONSENT_REQUIRED — consent must be asserted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/bookings":{"post":{"tags":["bookings"],"summary":"Create a booking","description":"Creates a real appointment at the provider. Requires a mandate (`mandateRef`) created from the user's instruction. The slot is revalidated at booking time. Check the service's `bookingRequirements` first (e.g. phone required) — missing details return `400` with the missing fields. Include a stable `idempotencyKey` so retries never double-book.","operationId":"createBooking","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookingCreate"}}}},"responses":{"200":{"description":"Confirmed booking","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Booking"}}}},"400":{"description":"Invalid request or missing customer information (see requiredFields)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"INVALID_REQUEST","message":"Additional customer information is required.","requiredFields":[{"id":"phone","type":"phone","label":"Phone number","required":true}]}}}},"409":{"description":"Slot taken — alternatives provided","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"SLOT_NO_LONGER_AVAILABLE","message":"That slot is no longer available.","alternatives":[]}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/bookings/{bookingId}":{"get":{"tags":["bookings"],"summary":"Retrieve a booking","description":"Live status of a booking by its AgentBookable id (`bb_bkg_*`). Customer personal data is partially masked.","operationId":"getBooking","parameters":[{"name":"bookingId","in":"path","required":true,"schema":{"type":"string"},"example":"bb_bkg_3e74e9ebd024c32b"}],"responses":{"200":{"description":"The booking","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Booking"}}}},"404":{"description":"Booking not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/bookings/{bookingId}/reschedule":{"post":{"tags":["bookings"],"summary":"Reschedule a booking (only when supported)","description":"Requires a mandate with action `reschedule_booking`. **Only available when the business `capabilities.reschedule` is true** — otherwise you get `RESCHEDULE_NOT_SUPPORTED`. Do not offer rescheduling to users for businesses where it is unsupported.","operationId":"rescheduleBooking","parameters":[{"name":"bookingId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RescheduleCreate"}}}},"responses":{"200":{"description":"Rescheduled booking","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Booking"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Reschedule not supported for this business","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}},"/bookings/{bookingId}/cancel":{"post":{"tags":["bookings"],"summary":"Cancel a booking (only when supported)","description":"Requires a mandate with action `cancel_booking`. **Only available when the business `capabilities.cancellation` is true** — otherwise you get `CANCELLATION_NOT_ALLOWED`. Do not offer cancellation to users for businesses where it is unsupported.","operationId":"cancelBooking","parameters":[{"name":"bookingId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CancelCreate"}}}},"responses":{"200":{"description":"Cancelled booking","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Booking"}}}},"400":{"description":"Invalid request","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"Cancellation not allowed for this business","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"CANCELLATION_NOT_ALLOWED","message":"This business does not allow cancellation."}}}},"429":{"$ref":"#/components/responses/RateLimited"}}}}},"components":{"responses":{"RateLimited":{"description":"Too many requests — slow down","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"RATE_LIMITED","message":"Too many requests. Retry later."}}}}},"schemas":{"Error":{"type":"object","required":["error","message"],"properties":{"error":{"type":"string","description":"Fixed error-code vocabulary","enum":["INVALID_REQUEST","AUTHENTICATION_FAILED","BUSINESS_NOT_FOUND","SERVICE_NOT_FOUND","SLOT_NO_LONGER_AVAILABLE","MANDATE_REQUIRED","MANDATE_INVALID","MANDATE_EXPIRED","MANDATE_ALREADY_USED","MANDATE_ACTION_MISMATCH","MANDATE_BUSINESS_MISMATCH","RATE_LIMITED","PROVIDER_ERROR","PROVIDER_UNAVAILABLE"]},"message":{"type":"string"},"requiredFields":{"type":"array","items":{"$ref":"#/components/schemas/Requirement"},"description":"Present on 400 when customer details are missing"},"alternatives":{"type":"array","items":{"type":"object"},"description":"Present on 409 SLOT_NO_LONGER_AVAILABLE"}}},"Business":{"type":"object","properties":{"id":{"type":"string","example":"ab_bus_c24850fce8cfb9db"},"name":{"type":"string","example":"AgentBookable Demo"},"slug":{"type":"string","nullable":true,"example":"agentbookable-demo"},"category":{"type":"string","nullable":true},"description":{"type":"string","nullable":true},"location":{"type":"object","properties":{"city":{"type":"string"},"postcode":{"type":"string"},"country":{"type":"string"}}},"timezone":{"type":"string","example":"Europe/Paris"},"provider":{"type":"object","properties":{"type":{"type":"string","description":"Internal provider family. Providers are never exposed by name to agents.","enum":["registry","calcom","zoho","simplybook"]}}},"capabilities":{"$ref":"#/components/schemas/Capabilities"}}},"Capabilities":{"type":"object","description":"What is possible for this business. Agents MUST respect these: do not promise actions whose flag is false.","properties":{"services":{"type":"boolean","description":"Service listing supported."},"availability":{"type":"boolean","description":"Live availability lookup supported. When true you may call POST /businesses/{id}/availability."},"booking":{"type":"boolean","description":"Booking creation supported. When true you may call POST /bookings (with a mandate)."},"reschedule":{"type":"boolean","description":"Rescheduling supported. When false, never offer to move an appointment."},"cancellation":{"type":"boolean","description":"Cancellation supported. When false, never offer to cancel."},"payment":{"type":"boolean","description":"Payment through AgentBookable. When false, payment (if any) is handled at the venue."},"instantConfirmation":{"type":"boolean","description":"True when bookings are confirmed immediately; false means the provider may hold requests for manual confirmation."}}},"Service":{"type":"object","properties":{"id":{"type":"string","example":"bb_srv_e64d99e1e1243d68"},"businessId":{"type":"string"},"name":{"type":"string","example":"Haircut"},"description":{"type":"string","nullable":true},"durationMinutes":{"type":"integer","example":30},"price":{"type":"object","nullable":true,"properties":{"amount":{"type":"number"},"currency":{"type":"string"}}},"staff":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}}}},"bookingRequirements":{"$ref":"#/components/schemas/BookingRequirements"}}},"BookingRequirements":{"type":"object","description":"What the agent must collect from the user BEFORE booking. Contact requirements are provider-derived; AgentBookable never reveals which provider.","properties":{"name":{"type":"string","enum":["required","optional"],"description":"Customer name."},"email":{"type":"string","enum":["required","optional"],"description":"Customer email."},"phone":{"type":"string","enum":["required","optional"],"description":"Customer phone. 'required' means the booking is rejected without one — collect it first."},"payment":{"type":"boolean","description":"True when payment is required to confirm."}}},"Requirement":{"type":"object","properties":{"id":{"type":"string","example":"phone"},"type":{"type":"string","enum":["text","email","phone","number","boolean","select"]},"label":{"type":"string"},"required":{"type":"boolean"}}},"AvailabilityResult":{"type":"object","properties":{"businessId":{"type":"string"},"serviceId":{"type":"string"},"slots":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","description":"Pass as slotId to POST /bookings"},"serviceId":{"type":"string"},"start":{"type":"string","description":"ISO-8601 with business-timezone offset"},"end":{"type":"string"},"staffId":{"type":"string","nullable":true},"fetchedAt":{"type":"string"},"expiresAt":{"type":"string","description":"Slots are temporary; book before this time or re-fetch"}}}}}},"MandateCreate":{"type":"object","required":["actorRef","action","instruction"],"properties":{"actorRef":{"type":"string","description":"Stable identifier for who is acting on the user's behalf (e.g. your agent/session id). Used in the audit trail.","example":"assistant-session-42"},"action":{"type":"string","enum":["create_booking","reschedule_booking","cancel_booking"]},"businessId":{"type":"string","description":"Bind the mandate to this business. Strongly recommended; a bound mandate cannot be replayed elsewhere.","example":"ab_bus_c24850fce8cfb9db"},"serviceId":{"type":"string","description":"Optionally bind to a specific service."},"instruction":{"type":"string","description":"The user's exact words. Do not paraphrase or invent. This is the consent evidence.","example":"Book the 09:00 haircut on 1 September."}}},"MandateCreated":{"type":"object","properties":{"mandateRef":{"type":"string","example":"mandate_76a00b335de7bd57"},"expiresAt":{"type":"string","description":"The mandate is invalid after this time (15 minutes)."}}},"BookingCreate":{"type":"object","required":["businessId","serviceId","customer","mandateRef"],"properties":{"businessId":{"type":"string"},"serviceId":{"type":"string"},"slotId":{"type":"string","description":"Slot id from the availability call (preferred)"},"slot":{"type":"object","description":"Explicit slot when you do not have a slotId","properties":{"start":{"type":"string"},"end":{"type":"string"},"staffId":{"type":"string"}}},"customer":{"type":"object","required":["name"],"properties":{"name":{"type":"string"},"email":{"type":"string","format":"email"},"phone":{"type":"string","description":"Required when the service's bookingRequirements.phone is 'required'"},"locale":{"type":"string"},"timezone":{"type":"string"}}},"mandateRef":{"type":"string","description":"From POST /mandates — single-use, expires in 15 minutes"},"idempotencyKey":{"type":"string","description":"Stable key for safe retries"},"draftId":{"type":"string","description":"The consented draft (from POST /booking-drafts + POST /mandates/for-draft). Request must match the draft exactly."}}},"Booking":{"type":"object","properties":{"id":{"type":"string","example":"bb_bkg_3e74e9ebd024c32b"},"businessId":{"type":"string"},"serviceId":{"type":"string"},"staffId":{"type":"string","nullable":true},"start":{"type":"string"},"end":{"type":"string"},"status":{"type":"string","enum":["confirmed","pending","cancelled","completed"]},"customer":{"type":"object","description":"Personal data partially masked","properties":{"name":{"type":"string"},"email":{"type":"string","nullable":true},"phone":{"type":"string","nullable":true}}},"createdVia":{"type":"string","enum":["agent"]},"mandateRef":{"type":"string"},"createdAt":{"type":"string"},"updatedAt":{"type":"string"}}},"RescheduleCreate":{"type":"object","required":["businessId","newStart","mandateRef"],"properties":{"businessId":{"type":"string"},"newStart":{"type":"string","description":"New start, ISO-8601 in the business timezone"},"mandateRef":{"type":"string"},"idempotencyKey":{"type":"string"}}},"CancelCreate":{"type":"object","required":["businessId","mandateRef"],"properties":{"businessId":{"type":"string"},"mandateRef":{"type":"string"},"reason":{"type":"string"},"idempotencyKey":{"type":"string"}}},"DraftCreate":{"type":"object","required":["businessId","serviceId","customer","actorRef"],"properties":{"businessId":{"type":"string"},"serviceId":{"type":"string"},"slotId":{"type":"string","description":"Slot id from the availability call (preferred)"},"slot":{"type":"object","properties":{"start":{"type":"string"},"end":{"type":"string"},"staffId":{"type":"string"}}},"customer":{"$ref":"#/components/schemas/BookingCreate/properties/customer"},"actorRef":{"type":"string","description":"Your agent/session identifier for the audit trail"}}},"BookingDraft":{"type":"object","description":"The proposal to show the user. Consequential fields are immutable; consent binds to this exact content (draftHash).","properties":{"id":{"type":"string","example":"draft_m1abc_def456"},"businessId":{"type":"string"},"serviceId":{"type":"string"},"serviceName":{"type":"string","example":"Haircut"},"start":{"type":"string","example":"2026-09-02T09:00:00+02:00"},"end":{"type":"string","example":"2026-09-02T09:30:00+02:00"},"durationMinutes":{"type":"integer","example":30},"timezone":{"type":"string","example":"Europe/Paris"},"staffId":{"type":"string","nullable":true},"customer":{"$ref":"#/components/schemas/BookingCreate/properties/customer"},"requirements":{"$ref":"#/components/schemas/BookingRequirements"},"expiresAt":{"type":"string","description":"Drafts expire after 30 minutes"}}},"ConsentCreate":{"type":"object","required":["actorRef","draftId","consent"],"properties":{"actorRef":{"type":"string"},"draftId":{"type":"string"},"consent":{"type":"object","required":["consentObtained","draftId"],"properties":{"consentObtained":{"type":"boolean","enum":[true],"description":"Assert that the user explicitly consented to this exact draft after seeing the final details."},"draftId":{"type":"string","description":"Must equal the draftId field"},"consentedBy":{"type":"string"},"consentMethod":{"type":"string","description":"e.g. 'chat reply: yes, book it'"},"instruction":{"type":"string","description":"The user's exact consenting words (audit context)"}}}}}}}}