{"openapi":"3.0.0","info":{"title":"Doctorsa LLM Booking API","version":"1.5.3","description":"LLM-safe subset of the Doctorsa booking flow.\n\n## Patient experience (agents — read first)\nThe patient delegates **100% of the booking** to you. They should never see APIs, MCP, REST, tools, sessions, polling, status codes, UUIDs, or field names.\n\n- **Be a concierge**, not a system operator — warm, confident, one short question at a time.\n- **Never ask permission** for routine steps (city, age, name, channel). **Slot selection is an exception:** always present all times (pricing once via `pricing_message`) and wait for the patient to choose. Only pause for answers, slot choice, OTP codes, and payment in the browser.\n- **MCP or REST** — use whichever works in your host; switch silently if MCP is blocked. Only fail after both are tried.\n- **After payment** — reassure while a doctor is matched; when assigned share doctor name, time, and `request_details_url` (appointment page on book.doctorsa.com).\n- **Errors** — translate to plain language; recover automatically when possible (wrong OTP → re-enter; lost link → `GET .../checkout`).\n\nFull rules: `GET /.well-known/agent.json` → `agentOperatingPrinciples` and `userFacingCommunication`.\n\n## Agent integration\n**Prefer the MCP protocol** (`GET /.well-known/mcp.json` or `GET /mcp` discovery → `POST /mcp`) over raw HTTP. MCP tools map to one patient question per step and return `missing` / `profile_complete` hints.\n\nIf MCP is unavailable, use this REST contract with the **same step-by-step flow** — do not tell the patient booking is impossible until both MCP and REST fail.\n\n## Agent identification\nNo custom `User-Agent` or `X-LLM-Agent` headers are required on any route (discovery, MCP JSON-RPC, or REST booking).\n\nOn `POST /sessions` only, send `agent_user_agent` and `agent_client` in the JSON body. MCP `create_booking_session` populates both automatically — never ask the patient.\n\n\n## Granular patient data collection\nCollect **one field per API call** — never batch city, age, language, or identity fields in a single user turn.\n\n| Step | Endpoint | Body field |\n|------|----------|------------|\n| Session start | `POST /sessions` | `country`, `care_type`, `agent_user_agent`, `agent_client` |\n| City | `PUT /sessions/{id}/city` | `city` |\n| Age | `PUT /sessions/{id}/age` | `age` |\n| Language | `PUT /sessions/{id}/language` | `language` — triggers eligibility + slots when context complete |\n| First name | `PUT /sessions/{id}/patient-info/first-name` | `first_name` |\n| Last name | `PUT /sessions/{id}/patient-info/last-name` | `last_name` |\n| Gender | `PUT /sessions/{id}/patient-info/gender` | `gender` |\n\n## Verification model\n**Verification is WhatsApp phone OTP OR email OTP** — one path per `communication_channel`, not both.\n\n| Channel | Verification | `checkout_url` returned by |\n|---------|--------------|---------------------------|\n| **sms** / **whatsapp** | WhatsApp OTP to `phone_e164` | `POST .../phone-verification/confirm` on success |\n| **email** | 5-digit OTP to `session.email` only (no phone/WhatsApp OTP) | `POST .../email-verification/confirm` on success |\n\n### Email channel details\n- Set the address via `PUT .../email` and/or `PUT .../communication-channel` with `email`.\n- Once `communication_channel=email` and an address is stored, it is **locked** until `POST .../email-verification/confirm` succeeds (`422 email_locked_pending_verification` if you try to change it).\n- `PUT .../email` is **optional** on whatsapp (Stripe prefill only).\n- On email OTP success the session becomes `verified` and `checkout_url` is returned.\n\n## Booking flow summary\n0. `GET  /verification-channels` — enabled whatsapp/email for this deploy\n1. `POST /sessions` — `country`, `care_type`\n2. `PUT  /sessions/{id}/city` → `PUT .../age` → `PUT .../language` (one field each; slots when context complete)\n3. Present all slot times; state pricing once via `pricing_message`; patient chooses → `PUT  /sessions/{id}/slot`\n4. `POST /sessions/{id}/need`\n5. `PUT  /sessions/{id}/communication-channel` — WhatsApp or email\n6. `PUT  /sessions/{id}/patient-info/first-name` → `.../last-name` → `.../gender` (one field each)\n7. `PUT  /sessions/{id}/email` — optional on whatsapp; required for email channel\n8. **whatsapp:** `POST /sessions/{id}/phone-verification` then `POST .../phone-verification/confirm` → `checkout_url`\n9. **email channel:** `POST /sessions/{id}/email-verification` then `POST .../email-verification/confirm` → `checkout_url`\n10. `GET  /sessions/{id}/checkout` — re-fetch `checkout_url` when payment is allowed\n11. `GET  /sessions/{id}` — poll until `paid` or `assigned`\n12. `DELETE /sessions/{id}` — cancel at any point\n\n`checkout_url` is a server redirect: `GET /v1/booking/payment/open-checkout/{stripe_checkout_session_id}` → 302 to Stripe hosted checkout. Use the URL verbatim from the API response.\n\n## Autonomous operation (agent hosts)\n\nAll `/v1/booking` operations in this spec are **bounded booking-session workflow steps**, not arbitrary CRUD.\nEach operation carries `x-openai-isConsequential: false` so OpenAI Actions and Microsoft Copilot OpenAPI plugins may offer \"Always allow\" without per-request confirmation.\n\nHuman steps that remain outside the API: patient OTP codes and Stripe Checkout in the patient browser.\n\nSee `GET /.well-known/agent.json` → `autonomousOperationPolicy` for the full trust model and MCP ToolAnnotations mapping."},"servers":[{"url":"https://findapi.doctorsa.com","description":"Production"}],"tags":[{"name":"booking","description":"LLM booking session lifecycle"}],"paths":{"/.well-known/mcp.json":{"get":{"operationId":"getMcpManifest","summary":"MCP discovery manifest","description":"Machine-readable MCP manifest for agents probing the API host directly.","x-agent-headers":"exempt","responses":{"200":{"description":"MCP manifest JSON"}}}},"/mcp":{"get":{"operationId":"getMcpDiscovery","summary":"MCP HTTP discovery document","description":"Tier-0 discovery for GET /mcp. POST to the same URL speaks JSON-RPC streamable HTTP MCP.","x-agent-headers":"exempt","responses":{"200":{"description":"Discovery JSON (not JSON-RPC)"}}},"options":{"operationId":"optionsMcp","summary":"MCP CORS / method discovery","x-agent-headers":"exempt","responses":{"204":{"description":"Allow: GET, POST, OPTIONS"}}},"post":{"operationId":"postMcpJsonRpc","summary":"MCP streamable HTTP JSON-RPC","description":"Tier-1 MCP protocol traffic. No custom agent headers required.","x-agent-headers":"exempt","tags":["booking"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","description":"JSON-RPC 2.0 envelope"}}}},"responses":{"200":{"description":"JSON-RPC response (streamable HTTP)"}}}},"/v1/booking/verification-channels":{"get":{"operationId":"getVerificationChannels","summary":"List enabled verification channels","description":"Returns whatsapp and email channels that set_communication_channel accepts on this deployment. Controlled by LLM_BOOKING_ENABLED_CHANNELS (default: whatsapp,email).","x-agent-headers":"exempt","tags":["booking"],"responses":{"200":{"description":"Enabled verification channels","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetVerificationChannelsResponse"}}}}},"x-openai-isConsequential":false,"x-agent-autonomous":true,"x-agent-destructive":false,"x-agent-open-world":false,"x-agent-scope":"bounded-booking-session"}},"/v1/booking/sessions":{"post":{"operationId":"createBookingSession","summary":"Start an eligibility-gated booking session","x-agent-headers":"exempt","description":"Requires country and care_type. Agent identification via JSON body (agent_user_agent and agent_client required). MCP create_booking_session populates body fields automatically.","tags":["booking"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateBookingSessionBody"}}}},"responses":{"201":{"description":"Session created (draft) or rejected at eligibility gate","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateBookingSessionResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"x-openai-isConsequential":false,"x-agent-autonomous":true,"x-agent-destructive":false,"x-agent-open-world":false,"x-agent-scope":"bounded-booking-session"}},"/v1/booking/sessions/{id}/need":{"post":{"operationId":"submitNeed","summary":"Classify the patient's medical need","tags":["booking"],"parameters":[{"$ref":"#/components/parameters/SessionId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubmitNeedBody"}}}},"responses":{"200":{"description":"Need outcome - allowed, clarification required, or rejected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubmitNeedResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"}},"x-agent-headers":"exempt","x-openai-isConsequential":false,"x-agent-autonomous":true,"x-agent-destructive":false,"x-agent-open-world":false,"x-agent-scope":"bounded-booking-session"}},"/v1/booking/sessions/{id}/city":{"put":{"operationId":"setSessionCity","summary":"Set patient city (one context field per call)","description":"Collect session context one field at a time. When city, age, and language are all set, eligibility runs on the final field and priced slots may be returned.","tags":["booking"],"parameters":[{"$ref":"#/components/parameters/SessionId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetSessionCityBody"}}}},"responses":{"200":{"description":"City saved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetSessionContextResponse"}}}},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"}},"x-agent-headers":"exempt","x-openai-isConsequential":false,"x-agent-autonomous":true,"x-agent-destructive":false,"x-agent-open-world":false,"x-agent-scope":"bounded-booking-session"}},"/v1/booking/sessions/{id}/age":{"put":{"operationId":"setSessionAge","summary":"Set patient age (one context field per call)","tags":["booking"],"parameters":[{"$ref":"#/components/parameters/SessionId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetSessionAgeBody"}}}},"responses":{"200":{"description":"Age saved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetSessionContextResponse"}}}},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"}},"x-agent-headers":"exempt","x-openai-isConsequential":false,"x-agent-autonomous":true,"x-agent-destructive":false,"x-agent-open-world":false,"x-agent-scope":"bounded-booking-session"}},"/v1/booking/sessions/{id}/language":{"put":{"operationId":"setSessionLanguage","summary":"Set preferred language (triggers eligibility when context complete)","tags":["booking"],"parameters":[{"$ref":"#/components/parameters/SessionId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetSessionLanguageBody"}}}},"responses":{"200":{"description":"Language saved; may include slots when context_complete","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetSessionContextResponse"}}}},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"}},"x-agent-headers":"exempt","x-openai-isConsequential":false,"x-agent-autonomous":true,"x-agent-destructive":false,"x-agent-open-world":false,"x-agent-scope":"bounded-booking-session"}},"/v1/booking/sessions/{id}/slot":{"put":{"operationId":"selectSlot","summary":"Select a preferred appointment slot offset","tags":["booking"],"parameters":[{"$ref":"#/components/parameters/SessionId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SelectSlotBody"}}}},"responses":{"200":{"description":"Slot accepted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SelectSlotResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"}},"x-agent-headers":"exempt","x-openai-isConsequential":false,"x-agent-autonomous":true,"x-agent-destructive":false,"x-agent-open-world":false,"x-agent-scope":"bounded-booking-session"}},"/v1/booking/sessions/{id}/communication-channel":{"put":{"operationId":"setCommunicationChannel","summary":"Set post-booking communication channel (whatsapp or email)","description":"Chooses how the patient is contacted after booking and which verification path applies.\n\n- **whatsapp**: phone verification via WhatsApp OTP on `phone_e164`.\n- **email**: email verification only (5-digit OTP to `email`); phone/WhatsApp OTP is **not** used.\n\nWhen `communication_channel=email`, provide a valid `email` in this request body **or** already set via `PUT .../email`.\n\nAllowed in `draft`, `phone_pending`, or `verified` (channel can be changed anytime before payment). Call `GET /verification-channels` first.\n","tags":["booking"],"parameters":[{"$ref":"#/components/parameters/SessionId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetCommunicationChannelBody"}}}},"responses":{"200":{"description":"Channel accepted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetCommunicationChannelResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"}},"x-agent-headers":"exempt","x-openai-isConsequential":false,"x-agent-autonomous":true,"x-agent-destructive":false,"x-agent-open-world":false,"x-agent-scope":"bounded-booking-session"}},"/v1/booking/sessions/{id}/patient-info/first-name":{"put":{"operationId":"setPatientFirstName","summary":"Set patient first name (one identity field per call)","tags":["booking"],"parameters":[{"$ref":"#/components/parameters/SessionId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetPatientFirstNameBody"}}}},"responses":{"200":{"description":"First name saved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetPatientFieldResponse"}}}},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"}},"x-agent-headers":"exempt","x-openai-isConsequential":false,"x-agent-autonomous":true,"x-agent-destructive":false,"x-agent-open-world":false,"x-agent-scope":"bounded-booking-session"}},"/v1/booking/sessions/{id}/patient-info/last-name":{"put":{"operationId":"setPatientLastName","summary":"Set patient last name (one identity field per call)","tags":["booking"],"parameters":[{"$ref":"#/components/parameters/SessionId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetPatientLastNameBody"}}}},"responses":{"200":{"description":"Last name saved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetPatientFieldResponse"}}}},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"}},"x-agent-headers":"exempt","x-openai-isConsequential":false,"x-agent-autonomous":true,"x-agent-destructive":false,"x-agent-open-world":false,"x-agent-scope":"bounded-booking-session"}},"/v1/booking/sessions/{id}/patient-info/gender":{"put":{"operationId":"setPatientGender","summary":"Set patient gender (one identity field per call)","tags":["booking"],"parameters":[{"$ref":"#/components/parameters/SessionId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetPatientGenderBody"}}}},"responses":{"200":{"description":"Gender saved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetPatientFieldResponse"}}}},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"}},"x-agent-headers":"exempt","x-openai-isConsequential":false,"x-agent-autonomous":true,"x-agent-destructive":false,"x-agent-open-world":false,"x-agent-scope":"bounded-booking-session"}},"/v1/booking/payment/success":{"get":{"operationId":"paymentRedirectSuccess","summary":"Technical success redirect endpoint for Stripe Checkout","description":"Returns a neutral confirmation payload.\nThis endpoint exists to satisfy Stripe Checkout redirect requirements;\npayment truth is still determined via webhook + booking status.\n","tags":["booking"],"responses":{"200":{"description":"Technical success redirect acknowledged","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentRedirectResponse"},"examples":{"success":{"value":{"ok":true,"payment_redirect":"success"}}}}}}},"x-agent-headers":"exempt","x-openai-isConsequential":false,"x-agent-autonomous":true,"x-agent-destructive":false,"x-agent-open-world":false,"x-agent-scope":"bounded-booking-session"}},"/v1/booking/payment/cancel":{"get":{"operationId":"paymentRedirectCancel","summary":"Technical cancel redirect endpoint for Stripe Checkout","description":"Returns a neutral confirmation payload.\nThis endpoint exists to satisfy Stripe Checkout redirect requirements;\npayment truth is still determined via webhook + booking status.\n","tags":["booking"],"responses":{"200":{"description":"Technical cancel redirect acknowledged","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaymentRedirectResponse"},"examples":{"cancel":{"value":{"ok":true,"payment_redirect":"cancel"}}}}}}},"x-agent-headers":"exempt","x-openai-isConsequential":false,"x-agent-autonomous":true,"x-agent-destructive":false,"x-agent-open-world":false,"x-agent-scope":"bounded-booking-session"}},"/v1/booking/payment/open-checkout/{stripeCheckoutSessionId}":{"get":{"operationId":"openCheckout","summary":"Redirect browser to Stripe hosted checkout","description":"302 redirect to the full Stripe Checkout URL (including init fragment).\nPatients open checkout_url from verify/checkout responses, which points here.\n","tags":["booking"],"parameters":[{"name":"stripeCheckoutSessionId","in":"path","required":true,"schema":{"type":"string","pattern":"^cs_(test|live)_"}}],"responses":{"302":{"description":"Redirect to Stripe hosted checkout"},"400":{"$ref":"#/components/responses/BadRequest"},"502":{"$ref":"#/components/responses/BadGateway"}},"x-agent-headers":"exempt","x-openai-isConsequential":false,"x-agent-autonomous":true,"x-agent-destructive":false,"x-agent-open-world":false,"x-agent-scope":"bounded-booking-session"}},"/v1/booking/sessions/{id}/email":{"put":{"operationId":"setEmail","summary":"Attach patient email (optional on whatsapp; required for email channel)","description":"Stores the patient email on the session.\n\n- **whatsapp**: optional; prefills Stripe Checkout `customer_email` if set before payment.\n- **email channel**: required before email verification (here or in `PUT .../communication-channel`). Email can be updated via this endpoint until verified.\n\nAllowed in `draft`, `phone_pending`, or `verified` before payment.\n","tags":["booking"],"parameters":[{"$ref":"#/components/parameters/SessionId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetEmailBody"}}}},"responses":{"200":{"description":"Email saved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetEmailResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"}},"x-agent-headers":"exempt","x-openai-isConsequential":false,"x-agent-autonomous":true,"x-agent-destructive":false,"x-agent-open-world":false,"x-agent-scope":"bounded-booking-session"}},"/v1/booking/sessions/{id}/phone-verification":{"post":{"operationId":"startPhoneVerification","summary":"Send phone OTP via WhatsApp — always required","description":"Sends a 6-digit OTP to `phone_e164` via **WhatsApp**. **Only when `communication_channel=whatsapp`** — returns `400 phone_verification_not_required` when channel is `email`.\n\nMoves session to `phone_pending`. Requires draft session with slot selected, need approved, and patient info complete.\n","tags":["booking"],"parameters":[{"$ref":"#/components/parameters/SessionId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StartPhoneVerificationBody"}}}},"responses":{"200":{"description":"OTP sent; session moves to phone_pending","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StartPhoneVerificationResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"x-agent-headers":"exempt","x-openai-isConsequential":false,"x-agent-autonomous":true,"x-agent-destructive":false,"x-agent-open-world":false,"x-agent-scope":"bounded-booking-session"}},"/v1/booking/sessions/{id}/phone-verification/confirm":{"post":{"operationId":"verifyPhoneOtp","summary":"Validate phone OTP and advance to verified","description":"Validates the 6-digit WhatsApp OTP. On success, `llm_booking_status` becomes `verified` and `checkout_url` is returned.\n\nOnly when `communication_channel=whatsapp`. Email channel uses `POST .../email-verification/confirm` instead.\n\nOn failure, status stays `phone_pending` with `error: invalid_otp`.\n","tags":["booking"],"parameters":[{"$ref":"#/components/parameters/SessionId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerifyPhoneOtpBody"}}}},"responses":{"200":{"description":"OTP accepted (verified) or rejected (invalid_otp)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerifyPhoneOtpResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"}},"x-agent-headers":"exempt","x-openai-isConsequential":false,"x-agent-autonomous":true,"x-agent-destructive":false,"x-agent-open-world":false,"x-agent-scope":"bounded-booking-session"}},"/v1/booking/sessions/{id}/email-verification":{"post":{"operationId":"startEmailVerification","summary":"Send 5-digit email verification code (email channel only)","description":"Email verification when `communication_channel=email`. Allowed in `draft` or `verified`.\n\nRequires a valid email on the session (from `PUT .../email` or `PUT .../communication-channel`). Request body `email` is optional — defaults to session email. If body `email` differs from session email, returns `422 email_mismatch`.\n\nOn confirm success, session becomes `verified` and `checkout_url` is returned. Does **not** require phone/WhatsApp OTP.\n\nReturns `status: email_code_sent`. Does **not** change `llm_booking_status` until confirm.\n","tags":["booking"],"parameters":[{"$ref":"#/components/parameters/SessionId"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StartEmailVerificationBody"}}}},"responses":{"200":{"description":"Email code sent; llm_booking_status remains verified","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StartEmailVerificationResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"429":{"$ref":"#/components/responses/TooManyRequests"}},"x-agent-headers":"exempt","x-openai-isConsequential":false,"x-agent-autonomous":true,"x-agent-destructive":false,"x-agent-open-world":false,"x-agent-scope":"bounded-booking-session"}},"/v1/booking/sessions/{id}/email-verification/confirm":{"post":{"operationId":"verifyEmailCode","summary":"Validate email code and unlock payment (email channel only)","description":"Validates the 5-digit code sent to the session email. On success sets `email_verified_at` and returns `status: email_verified`. Returns `checkout_url` when phone is already verified (payment gate cleared); otherwise `email_verified` without checkout.\n\nOptional body `email` must match session email or returns `422 email_mismatch`.\n\nOnly applicable when `communication_channel=email`. Not used for whatsapp bookings.\n","tags":["booking"],"parameters":[{"$ref":"#/components/parameters/SessionId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerifyEmailCodeBody"}}}},"responses":{"200":{"description":"Code accepted (email_verified + checkout_url) or rejected (invalid_otp, status stays verified)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerifyEmailCodeResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"}},"x-agent-headers":"exempt","x-openai-isConsequential":false,"x-agent-autonomous":true,"x-agent-destructive":false,"x-agent-open-world":false,"x-agent-scope":"bounded-booking-session"}},"/v1/booking/sessions/{id}/checkout":{"get":{"operationId":"getPaymentLink","summary":"Get (or re-fetch) checkout_url when payment is allowed","description":"Returns `checkout_url` (server open-checkout redirect to Stripe hosted checkout).\n\nRequires `llm_booking_status=verified` **and** payment gate cleared:\n- **whatsapp**: allowed immediately after phone OTP.\n- **email**: allowed only after `POST .../email-verification/confirm` (`email_verified_at` set). Otherwise `422 email_verification_required`.\n\nSame `checkout_url` shape as `phone-verification/confirm` (whatsapp) or `email-verification/confirm` (email channel). No request body.\n","tags":["booking"],"parameters":[{"$ref":"#/components/parameters/SessionId"}],"responses":{"200":{"description":"checkout_url and stripe_checkout_session_id","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetPaymentLinkResponse"}}}},"404":{"$ref":"#/components/responses/NotFound"},"422":{"$ref":"#/components/responses/BadRequest"}},"x-agent-headers":"exempt","x-openai-isConsequential":false,"x-agent-autonomous":true,"x-agent-destructive":false,"x-agent-open-world":false,"x-agent-scope":"bounded-booking-session"}},"/v1/booking/sessions/{id}/verification":{"delete":{"operationId":"resetVerification","summary":"Reset phone verification and return session to draft","tags":["booking"],"parameters":[{"$ref":"#/components/parameters/SessionId"}],"responses":{"200":{"description":"Session reset to draft","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResetVerificationResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"}},"x-agent-headers":"exempt","x-openai-isConsequential":false,"x-agent-autonomous":true,"x-agent-destructive":false,"x-agent-open-world":false,"x-agent-scope":"bounded-booking-session"}},"/v1/booking/sessions/{id}":{"get":{"operationId":"getBookingStatus","summary":"Get booking session (status and details)","description":"Returns `status` from `llm_booking_status` (session lifecycle). When `communication_channel=email`, also returns `email_verified` (boolean) and `email_verified_at` when set — secondary email verification does not change `status` (stays `verified` until payment).\n","tags":["booking"],"parameters":[{"$ref":"#/components/parameters/SessionId"}],"responses":{"200":{"description":"Current session status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetBookingStatusResponse"}}}},"404":{"$ref":"#/components/responses/NotFound"}},"x-agent-headers":"exempt","x-openai-isConsequential":false,"x-agent-autonomous":true,"x-agent-destructive":false,"x-agent-open-world":false,"x-agent-scope":"bounded-booking-session"},"delete":{"operationId":"cancelBookingSession","summary":"Cancel booking session (with refund if paid and unassigned)","tags":["booking"],"parameters":[{"$ref":"#/components/parameters/SessionId"}],"responses":{"200":{"description":"Session cancelled; refund_status indicates whether a Stripe refund was issued","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CancelBookingSessionResponse"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"},"502":{"$ref":"#/components/responses/BadGateway"}},"x-agent-headers":"exempt","x-openai-isConsequential":false,"x-agent-autonomous":true,"x-agent-destructive":false,"x-agent-open-world":false,"x-agent-scope":"bounded-booking-session"}}},"components":{"parameters":{"SessionId":{"name":"id","in":"path","required":true,"description":"booking_session_id returned by POST /sessions","schema":{"type":"string"}}},"responses":{"BadRequest":{"description":"Invalid input or business-rule violation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"NotFound":{"description":"Session not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"Conflict":{"description":"Session is in a terminal or incompatible state","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"TooManyRequests":{"description":"OTP or eligibility rate limit exceeded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimitErrorResponse"}}}},"BadGateway":{"description":"Upstream provider error (e.g. Stripe refund failure)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"schemas":{"ErrorResponse":{"type":"object","required":["statusCode","message"],"properties":{"statusCode":{"type":"integer"},"message":{"type":"string"},"error":{"type":"string"}}},"RateLimitErrorResponse":{"type":"object","required":["error","retry_after_seconds","retry_after_iso"],"properties":{"error":{"type":"string","example":"rate_limit_exceeded"},"retry_after_seconds":{"type":"integer","description":"Seconds remaining before a new OTP request will be accepted"},"retry_after_iso":{"type":"string","format":"date-time","description":"Absolute ISO 8601 UTC timestamp after which a new OTP request will be accepted. Use this to tell the user exactly when to retry (e.g. 'You can try again after 2026-06-25T14:30:00.000Z')."}}},"CancelReason":{"type":"string","enum":["user_cancelled_pre_payment","user_cancelled_pre_assignment","user_cancelled_post_assignment","no_doctor_accept","payment_failed","payment_disputed","session_expired","admin_cancelled"]},"RejectionReasonCode":{"type":"string","enum":["no_doctors_online","tta_too_high","no_data","country_not_supported","out_of_hours","language_not_supported","urgent_care_only"]},"RejectionType":{"type":"string","enum":["not_eligible","not_allowed","insufficient_information"]},"PaymentRedirectResponse":{"type":"object","required":["ok","payment_redirect"],"properties":{"ok":{"type":"boolean","enum":[true]},"payment_redirect":{"type":"string","enum":["success","cancel"]}}},"BookingSlotOption":{"type":"object","required":["slot_offset_minutes"],"properties":{"slot_offset_minutes":{"type":"integer","description":"Minutes from slot_reference_at (before payment) or paid_at (after payment) when the appointment is scheduled"},"local_time":{"type":"string","description":"Clock time in patient_timezone, e.g. \"3:20 PM\" — present this (or local_time_label) to the patient"},"local_time_label":{"type":"string","description":"Patient-friendly label, e.g. \"Today at 3:20 PM\""}}},"BookingMorningSlotOption":{"type":"object","required":["starts_at"],"properties":{"starts_at":{"type":"string","format":"date-time","description":"ISO-8601 start time for the morning appointment (UTC representing local time in booking country timezone)"},"local_time":{"type":"string","description":"Clock time in patient_timezone, e.g. \"8:30 AM\""},"local_time_label":{"type":"string","description":"Patient-friendly label, e.g. \"Tomorrow at 8:30 AM\""}}},"CreateBookingSessionBody":{"type":"object","required":["country","care_type","agent_user_agent","agent_client"],"properties":{"country":{"type":"string","description":"ISO 3166-1 alpha-2 country code (e.g. \"IT\")"},"care_type":{"type":"string","description":"Care intent; currently only \"urgent\" is accepted"},"agent_user_agent":{"type":"string","description":"Required. Raw client string. MCP auto-fills."},"agent_client":{"type":"string","enum":["cursor","claude","chatgpt","gemini","copilot","perplexity","mcp","other"],"description":"Required normalized slug (unknown values stored as other). MCP auto-fills."}}},"SetSessionCityBody":{"type":"object","required":["city"],"properties":{"city":{"type":"string","description":"Patient city name"}}},"SetSessionAgeBody":{"type":"object","required":["age"],"properties":{"age":{"type":"integer","minimum":0,"maximum":120}}},"SetSessionLanguageBody":{"type":"object","required":["language"],"properties":{"language":{"type":"string","description":"Preferred language code (e.g. \"it\", \"en\", \"es\")"}}},"SetSessionContextResponse":{"type":"object","required":["ok","expires_at","context_complete","missing"],"properties":{"ok":{"type":"boolean","enum":[true]},"expires_at":{"type":"string","format":"date-time"},"context_complete":{"type":"boolean"},"missing":{"type":"array","items":{"type":"string","enum":["city","age","language"]}},"booking_session_id":{"type":"string"},"status":{"type":"string","enum":["draft","rejected"]},"price_amount":{"type":"number"},"price_currency":{"type":"string"},"pricing_message":{"type":"string","description":"Patient-facing pricing line — state once when presenting slots"},"patient_timezone":{"type":"string","description":"IANA timezone used for local_time / local_time_label on slots"},"slot_reference_at":{"type":"string","format":"date-time","description":"UTC anchor when offset slots were computed"},"slots":{"type":"array","items":{"$ref":"#/components/schemas/BookingSlotOption"}},"morning_slots":{"type":"array","items":{"$ref":"#/components/schemas/BookingMorningSlotOption"}},"rejection_type":{"type":"string"},"rejection_reason_code":{"type":"string"},"user_message":{"type":"string"}}},"CreateBookingSessionResponse":{"oneOf":[{"$ref":"#/components/schemas/CreateBookingSessionDraftResponse"},{"$ref":"#/components/schemas/CreateBookingSessionRejectedResponse"}],"discriminator":{"propertyName":"status","mapping":{"draft":"#/components/schemas/CreateBookingSessionDraftResponse","rejected":"#/components/schemas/CreateBookingSessionRejectedResponse"}}},"CreateBookingSessionDraftResponse":{"type":"object","required":["booking_session_id","status","expires_at"],"properties":{"booking_session_id":{"type":"string"},"status":{"type":"string","enum":["draft"]},"context_complete":{"type":"boolean","description":"False until city, age, and language are set via granular PUT endpoints"},"missing":{"type":"array","items":{"type":"string","enum":["city","age","language"]}},"price_amount":{"type":"number","description":"Present after context_complete and eligibility pass"},"price_currency":{"type":"string","description":"ISO 4217 currency code (e.g. \"EUR\")"},"slots":{"type":"array","items":{"$ref":"#/components/schemas/BookingSlotOption"},"description":"Available slot options with per-slot pricing"},"morning_slots":{"type":"array","items":{"$ref":"#/components/schemas/BookingMorningSlotOption"},"description":"Morning appointment options with per-slot pricing (night hours only)"},"expires_at":{"type":"string","format":"date-time","nullable":true}}},"CreateBookingSessionRejectedResponse":{"type":"object","required":["booking_session_id","status","rejection_type","rejection_reason_code","user_message"],"properties":{"booking_session_id":{"type":"string"},"status":{"type":"string","enum":["rejected"]},"rejection_type":{"type":"string","enum":["not_eligible"]},"rejection_reason_code":{"$ref":"#/components/schemas/RejectionReasonCode"},"user_message":{"type":"string","description":"Human-readable rejection message to show the patient"}}},"SubmitNeedBody":{"type":"object","required":["need"],"properties":{"need":{"type":"string","description":"Free-text description of the patient's medical need"}}},"SubmitNeedResponse":{"oneOf":[{"$ref":"#/components/schemas/SubmitNeedAllowedResponse"},{"$ref":"#/components/schemas/SubmitNeedClarificationResponse"},{"$ref":"#/components/schemas/SubmitNeedRejectedResponse"}],"discriminator":{"propertyName":"allowed_check_outcome","mapping":{"allowed":"#/components/schemas/SubmitNeedAllowedResponse","clarification_required":"#/components/schemas/SubmitNeedClarificationResponse","not_allowed":"#/components/schemas/SubmitNeedRejectedResponse"}}},"SubmitNeedAllowedResponse":{"type":"object","required":["booking_session_id","status","allowed_check_outcome","allowed","next_step","message_to_user"],"properties":{"booking_session_id":{"type":"string"},"status":{"type":"string","enum":["draft"]},"allowed_check_outcome":{"type":"string","enum":["allowed"]},"allowed":{"type":"boolean","enum":[true]},"next_step":{"type":"string","enum":["phone_verification"]},"message_to_user":{"type":"string"}}},"SubmitNeedClarificationResponse":{"type":"object","required":["booking_session_id","status","allowed_check_outcome","message_to_user"],"properties":{"booking_session_id":{"type":"string"},"status":{"type":"string","enum":["draft"]},"allowed_check_outcome":{"type":"string","enum":["clarification_required"]},"message_to_user":{"type":"string","description":"Follow-up question to ask the patient"}}},"SubmitNeedRejectedResponse":{"type":"object","required":["booking_session_id","status","allowed_check_outcome","rejection_type","message_to_user"],"properties":{"booking_session_id":{"type":"string"},"status":{"type":"string","enum":["rejected"]},"allowed_check_outcome":{"type":"string","enum":["not_allowed","clarification_required"]},"rejection_type":{"$ref":"#/components/schemas/RejectionType"},"message_to_user":{"type":"string"}}},"SelectSlotBody":{"type":"object","required":["slot_offset_minutes"],"properties":{"slot_offset_minutes":{"type":"integer","enum":[20,30,40,60],"description":"Preferred appointment offset in minutes from now"}}},"SelectSlotResponse":{"type":"object","required":["ok","selected_slot_offset_minutes","expires_at"],"properties":{"ok":{"type":"boolean","enum":[true]},"selected_slot_offset_minutes":{"type":"integer"},"expires_at":{"type":"string","format":"date-time"}}},"GetVerificationChannelsResponse":{"type":"object","required":["enabled_channels"],"properties":{"enabled_channels":{"type":"array","items":{"type":"string","enum":["whatsapp","email"]},"description":"Channels accepted by set_communication_channel on this deployment"}}},"SetCommunicationChannelBody":{"type":"object","required":["communication_channel"],"properties":{"communication_channel":{"type":"string","enum":["whatsapp","email"],"description":"Post-booking contact preference."},"email":{"type":"string","format":"email","description":"Required when communication_channel=email unless already set via PUT .../email. Locked after set until email verification completes."}}},"SetCommunicationChannelResponse":{"type":"object","required":["ok","communication_channel","email_verification_required","expires_at"],"properties":{"ok":{"type":"boolean","enum":[true]},"communication_channel":{"type":"string","enum":["whatsapp","email"]},"phone_verification_path":{"type":"string","enum":["whatsapp"]},"email_verification_required":{"type":"boolean","description":"true when communication_channel=email — a separate email verification step is required before payment."},"expires_at":{"type":"string","format":"date-time"}}},"SetEmailBody":{"type":"object","required":["email"],"properties":{"email":{"type":"string","format":"email"}}},"SetEmailResponse":{"type":"object","required":["ok","expires_at"],"properties":{"ok":{"type":"boolean","enum":[true]},"expires_at":{"type":"string","format":"date-time"}}},"StartPhoneVerificationBody":{"type":"object","required":["phone_e164"],"properties":{"phone_e164":{"type":"string","pattern":"^\\+[1-9]\\d{7,14}$","description":"Patient phone in E.164 format (e.g. \"+39xxxxxxxxxx\")"}}},"StartEmailVerificationBody":{"type":"object","properties":{"email":{"type":"string","format":"email","description":"Optional; defaults to session email. Must match session email when provided."}}},"StartPhoneVerificationResponse":{"type":"object","required":["booking_session_id","status","retry_after_seconds","retry_after_iso"],"properties":{"booking_session_id":{"type":"string"},"status":{"type":"string","enum":["phone_pending"]},"retry_after_seconds":{"type":"integer","description":"Seconds to wait before the user can request another OTP"},"retry_after_iso":{"type":"string","format":"date-time","description":"Absolute ISO 8601 UTC timestamp after which another OTP request will be accepted"}}},"StartEmailVerificationResponse":{"type":"object","required":["booking_session_id","status","retry_after_seconds","retry_after_iso"],"properties":{"booking_session_id":{"type":"string"},"status":{"type":"string","enum":["email_code_sent"]},"retry_after_seconds":{"type":"integer","description":"Seconds to wait before the user can request another email code"},"retry_after_iso":{"type":"string","format":"date-time","description":"Absolute ISO 8601 UTC timestamp after which another email code request will be accepted"}}},"VerifyEmailCodeBody":{"type":"object","required":["code"],"properties":{"code":{"type":"string","description":"5-digit code received by email"},"email":{"type":"string","format":"email","description":"Optional; must match session email when provided (422 email_mismatch otherwise)."}}},"VerifyPhoneOtpBody":{"type":"object","required":["otp_code"],"properties":{"otp_code":{"type":"string","description":"6-digit OTP received via WhatsApp"}}},"VerifyPhoneOtpResponse":{"oneOf":[{"$ref":"#/components/schemas/VerifyPhoneOtpSuccessResponse"},{"$ref":"#/components/schemas/VerifyPhoneOtpAwaitingEmailResponse"},{"$ref":"#/components/schemas/VerifyPhoneOtpFailureResponse"}],"description":"On success, status is verified. Distinguish whatsapp (checkout_url present) from email channel (email_verification_required: true, no checkout_url)."},"VerifyPhoneOtpSuccessResponse":{"type":"object","required":["booking_session_id","status","checkout_url","stripe_checkout_session_id","price_amount","price_currency"],"properties":{"booking_session_id":{"type":"string"},"status":{"type":"string","enum":["verified"]},"checkout_url":{"type":"string","description":"Server redirect URL (GET → 302 to Stripe hosted checkout). Use verbatim; do not rebuild from stripe_checkout_session_id. Returned for whatsapp channel only."},"stripe_checkout_session_id":{"type":"string"},"price_amount":{"type":"number","description":"Consultation price in major currency units (e.g. 55)"},"price_currency":{"type":"string","description":"ISO 4217 currency code (e.g. \"eur\")"}}},"VerifyPhoneOtpAwaitingEmailResponse":{"type":"object","required":["booking_session_id","status","email_verification_required","email","message_to_user"],"properties":{"booking_session_id":{"type":"string"},"status":{"type":"string","enum":["verified"]},"email_verification_required":{"type":"boolean","enum":[true],"description":"Payment blocked until POST .../email-verification/confirm succeeds."},"email":{"type":"string","format":"email","description":"Locked session email — use for start_email_verification."},"message_to_user":{"type":"string"}}},"VerifyEmailCodeResponse":{"oneOf":[{"$ref":"#/components/schemas/VerifyEmailCodeSuccessResponse"},{"$ref":"#/components/schemas/VerifyEmailCodeFailureResponse"}],"description":"On success returns email_verified with checkout_url. On failure llm_booking_status stays verified."},"VerifyEmailCodeSuccessResponse":{"type":"object","required":["booking_session_id","status","email","checkout_url","stripe_checkout_session_id","price_amount","price_currency"],"properties":{"booking_session_id":{"type":"string"},"status":{"type":"string","enum":["email_verified"]},"email":{"type":"string","format":"email"},"checkout_url":{"type":"string","description":"Server redirect URL (GET → 302 to Stripe hosted checkout). Payment gate cleared."},"stripe_checkout_session_id":{"type":"string"},"price_amount":{"type":"number"},"price_currency":{"type":"string"}}},"VerifyEmailCodeFailureResponse":{"type":"object","required":["booking_session_id","status","error","message_to_user"],"properties":{"booking_session_id":{"type":"string"},"status":{"type":"string","enum":["verified"],"description":"llm_booking_status unchanged on invalid code"},"error":{"type":"string","enum":["invalid_otp"]},"message_to_user":{"type":"string"}}},"VerifyPhoneOtpFailureResponse":{"type":"object","required":["booking_session_id","status","error","message_to_user"],"properties":{"booking_session_id":{"type":"string"},"status":{"type":"string","enum":["phone_pending"]},"error":{"type":"string","enum":["invalid_otp"]},"message_to_user":{"type":"string"}}},"ResetVerificationResponse":{"type":"object","required":["ok","status"],"properties":{"ok":{"type":"boolean","enum":[true]},"status":{"type":"string","enum":["draft"]}}},"GetBookingStatusResponse":{"oneOf":[{"$ref":"#/components/schemas/GetBookingStatusBaseResponse"},{"$ref":"#/components/schemas/GetBookingStatusAssignedResponse"},{"$ref":"#/components/schemas/GetBookingStatusCancelledResponse"},{"$ref":"#/components/schemas/GetBookingStatusRejectedResponse"}],"discriminator":{"propertyName":"status","mapping":{"draft":"#/components/schemas/GetBookingStatusBaseResponse","phone_pending":"#/components/schemas/GetBookingStatusBaseResponse","verified":"#/components/schemas/GetBookingStatusBaseResponse","paid":"#/components/schemas/GetBookingStatusBaseResponse","expired":"#/components/schemas/GetBookingStatusBaseResponse","assigned":"#/components/schemas/GetBookingStatusAssignedResponse","cancelled":"#/components/schemas/GetBookingStatusCancelledResponse","rejected":"#/components/schemas/GetBookingStatusRejectedResponse"}}},"GetBookingStatusBaseResponse":{"type":"object","required":["booking_session_id","status","price_amount","price_currency"],"properties":{"booking_session_id":{"type":"string"},"status":{"type":"string","enum":["draft","phone_pending","verified","paid","expired"]},"price_amount":{"type":"number"},"price_currency":{"type":"string"},"selected_slot_offset_minutes":{"type":"integer"},"email_verified":{"type":"boolean","description":"Present when communication_channel=email. true after POST .../email-verification/confirm succeeds. Independent of status (which remains verified until paid)."},"email_verified_at":{"type":"string","format":"date-time","description":"Present when communication_channel=email and email_verified=true."}}},"GetBookingStatusAssignedResponse":{"type":"object","required":["booking_session_id","status","price_amount","price_currency","appointment_time","doctor_name","video_url","request_details_url","message_to_user"],"properties":{"booking_session_id":{"type":"string"},"status":{"type":"string","enum":["assigned"]},"price_amount":{"type":"number"},"price_currency":{"type":"string"},"selected_slot_offset_minutes":{"type":"integer"},"appointment_time":{"type":"string","format":"date-time","nullable":true},"doctor_name":{"type":"string"},"video_url":{"type":"string","nullable":true},"request_details_url":{"type":"string","format":"uri","description":"Patient appointment page on book.doctorsa.com. Pattern: https://book.doctorsa.com/requests/{booking_session_id}. Agents must share this URL with the patient when status=assigned."},"message_to_user":{"type":"string","description":"Plain-language summary for the patient; includes appointment link when assigned."},"email_verified":{"type":"boolean","description":"Present when communication_channel=email."},"email_verified_at":{"type":"string","format":"date-time","description":"Present when communication_channel=email and email_verified=true."}}},"GetBookingStatusCancelledResponse":{"type":"object","required":["booking_session_id","status","price_amount","price_currency","cancel_reason"],"properties":{"booking_session_id":{"type":"string"},"status":{"type":"string","enum":["cancelled"]},"price_amount":{"type":"number"},"price_currency":{"type":"string"},"selected_slot_offset_minutes":{"type":"integer"},"cancel_reason":{"allOf":[{"$ref":"#/components/schemas/CancelReason"}],"nullable":true},"email_verified":{"type":"boolean","description":"Present when communication_channel=email."},"email_verified_at":{"type":"string","format":"date-time","description":"Present when communication_channel=email and email_verified=true."}}},"GetBookingStatusRejectedResponse":{"type":"object","required":["booking_session_id","status","price_amount","price_currency","rejection_type","rejection_reason_code"],"properties":{"booking_session_id":{"type":"string"},"status":{"type":"string","enum":["rejected"]},"price_amount":{"type":"number"},"price_currency":{"type":"string"},"selected_slot_offset_minutes":{"type":"integer"},"rejection_type":{"allOf":[{"$ref":"#/components/schemas/RejectionType"}],"nullable":true},"rejection_reason_code":{"allOf":[{"$ref":"#/components/schemas/RejectionReasonCode"}],"nullable":true},"email_verified":{"type":"boolean","description":"Present when communication_channel=email."},"email_verified_at":{"type":"string","format":"date-time","description":"Present when communication_channel=email and email_verified=true."}}},"SetPatientFirstNameBody":{"type":"object","required":["first_name"],"properties":{"first_name":{"type":"string"}}},"SetPatientLastNameBody":{"type":"object","required":["last_name"],"properties":{"last_name":{"type":"string"}}},"SetPatientGenderBody":{"type":"object","required":["gender"],"properties":{"gender":{"type":"string","enum":["M","F","male","female"],"description":"Accepted as M/F or male/female (case-insensitive)"}}},"SetPatientFieldResponse":{"type":"object","required":["ok","expires_at","profile_complete","missing"],"properties":{"ok":{"type":"boolean","enum":[true]},"expires_at":{"type":"string","format":"date-time"},"profile_complete":{"type":"boolean"},"missing":{"type":"array","items":{"type":"string","enum":["first_name","last_name","gender"]}}}},"GetPaymentLinkResponse":{"type":"object","required":["booking_session_id","status","checkout_url","stripe_checkout_session_id","price_amount","price_currency"],"properties":{"booking_session_id":{"type":"string"},"status":{"type":"string","enum":["verified"]},"checkout_url":{"type":"string","description":"Server redirect URL (GET → 302 to Stripe hosted checkout). Use verbatim; do not rebuild from stripe_checkout_session_id."},"stripe_checkout_session_id":{"type":"string"},"price_amount":{"type":"number","description":"Consultation price in major currency units (e.g. 55)"},"price_currency":{"type":"string","description":"ISO 4217 currency code (e.g. \"eur\")"}}},"CancelBookingSessionResponse":{"type":"object","required":["booking_session_id","status","cancel_reason"],"properties":{"booking_session_id":{"type":"string"},"status":{"type":"string","enum":["cancelled"]},"cancel_reason":{"allOf":[{"$ref":"#/components/schemas/CancelReason"}],"nullable":true},"refund_status":{"type":"string","enum":["not_required","succeeded","failed"],"description":"Present when the session was paid at the time of cancellation. \"not_required\" means no charge was collected or doctor was already assigned. \"succeeded\" / \"failed\" indicate Stripe refund outcome.\n"}}}}}}