{"openapi":"3.1.0","info":{"title":"AI Receptionist API","version":"0.0.0","description":"Clinic configuration and appointment scheduling."},"servers":[{"url":"/","description":"Current server"}],"security":[{"bearerAuth":[]}],"x-auth-mode":"bearer","paths":{"/api/v1/book/{dialed_number}":{"post":{"tags":["Appointments"],"operationId":"post-api-v1-book-dialed-number","requestBody":{"required":true,"description":"Flat JSON object, at most 16 KiB. Do not wrap this body in payload.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BookAppointmentRequest"},"example":{"date":"2026-09-25","time":"11:30 AM","patient_name":"Aradhana","patient_number":"+918881014378","doctor_id":"doctor_001"}}}},"summary":"Book a doctor's appointment slot","description":"Loads the clinic and doctor using the dialed number and doctor_id, then rechecks availability before inserting a Google Calendar event. The requested time must align with the day's working-period grid, spaced by appointment.duration_minutes + buffer_minutes. Off-grid or busy times return nearest free alternatives without creating an event. The title is patient_name and the description is patient_number. The event lasts appointment.duration_minutes; buffer_minutes is reserved after it. This modifies the connected calendar, including in development mode.","parameters":[{"name":"dialed_number","in":"path","required":true,"schema":{"type":"string","pattern":"^\\+?[1-9]\\d{7,14}$"},"example":"918065354358"}],"responses":{"201":{"description":"Appointment booked in Google Calendar.","content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["status"],"properties":{"status":{"type":"string","enum":["booked"]}}},"example":{"status":"booked"}}}},"400":{"description":"Malformed JSON, invalid fields, or missing booking fields.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppointmentError"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No clinic JSON exists for the dialed number, or doctor_id is not in that clinic.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppointmentError"}}}},"409":{"description":"The requested time is off-grid or unavailable, or Google Calendar reported a conflicting change. An unavailable result includes up to two nearest free alternatives.","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/UnavailableAppointment"},{"$ref":"#/components/schemas/AppointmentError"}]}}}},"413":{"description":"Request exceeds 16 KiB.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppointmentError"}}}},"415":{"description":"Content-Type must be application/json.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppointmentError"}}}},"500":{"$ref":"#/components/responses/ServerError"},"502":{"description":"Google Calendar failed or returned an invalid response. Inspect the calendar before retrying an ambiguous write.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppointmentError"}}}},"503":{"description":"Clinic configuration, Google credentials, or calendar access is unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppointmentError"}}}}}}},"/api/v1/check/{dialed_number}":{"post":{"tags":["Appointments"],"operationId":"post-api-v1-check-dialed-number","requestBody":{"required":true,"description":"Flat JSON body, at most 16 KiB. date and doctor_id are required at the top level, together with exactly one of time or time_window. payload wrappers are rejected.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppointmentCheckRequest"},"examples":{"timeWindow":{"value":{"date":"2026-09-28","time_window":"morning","doctor_id":"doctor_001"}},"exactTime":{"value":{"date":"2026-09-28","time":"09:30 AM","doctor_id":"doctor_001"}}}}}},"summary":"Check a doctor's appointment slot","description":"Loads the clinic JSON from clinics/{dialed_number} and matches doctor_id to doctors[].id. Provide exactly one of time or time_window in a flat JSON body; payload wrappers are not supported. All times use the clinic timezone and the working-period grid spaced by appointment.duration_minutes + buffer_minutes. Exact-time checks report availability or the two nearest free alternatives, with later-day fallback through 30 days when needed. Time-window checks return the first two free slots on the requested date: morning starts before noon, afternoon starts at or after noon and before 3 PM, evening starts at or after 3 PM. The day grid is not restarted at window boundaries. Window checks never fall back to another window or date. Past slots are excluded.","parameters":[{"name":"dialed_number","in":"path","required":true,"schema":{"type":"string","pattern":"^\\+?[1-9]\\d{7,14}$"},"description":"Clinic phone digits with an optional leading +. The folder name contains digits only.","example":"918065354358"}],"responses":{"200":{"description":"Exact-time checks report availability or nearest free alternatives. Window checks always include alternatives: one or two earliest free slots with status available, or an empty array with status unavailable.","content":{"application/json":{"schema":{"oneOf":[{"type":"object","additionalProperties":false,"required":["status"],"properties":{"status":{"type":"string","enum":["available"]},"alternatives":{"$ref":"#/components/schemas/AppointmentAlternatives"}}},{"$ref":"#/components/schemas/UnavailableAppointment"}]},"examples":{"available":{"value":{"status":"available"}},"windowAvailable":{"value":{"status":"available","alternatives":[{"date":"2026-09-28","time":"09:00 AM"},{"date":"2026-09-28","time":"09:25 AM"}]}},"windowUnavailable":{"value":{"status":"unavailable","alternatives":[]}},"unavailable":{"value":{"status":"unavailable","alternatives":[{"date":"2026-09-28","time":"09:25 AM"},{"date":"2026-09-28","time":"09:50 AM"}]}}}}}},"400":{"description":"Malformed JSON, a payload wrapper, invalid date/time, time_window or dialed number, missing fields, or both time and time_window supplied.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppointmentError"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"404":{"description":"No clinic JSON exists for the dialed number, or doctor_id is not in that clinic.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppointmentError"}}}},"413":{"description":"Request exceeds 16 KiB.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppointmentError"}}}},"415":{"description":"Content-Type must be application/json.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppointmentError"}}}},"500":{"$ref":"#/components/responses/ServerError"},"502":{"description":"Google Calendar failed or returned an invalid response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppointmentError"}}}},"503":{"description":"Clinic configuration, Google credentials, or calendar access is unavailable.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppointmentError"}}}}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer"}},"responses":{"Unauthorized":{"description":"Missing, malformed, or incorrect bearer token."},"Forbidden":{"description":"The browser origin is not allowed."},"ServerError":{"description":"Server authentication is not configured, or an internal error occurred."}},"schemas":{"BookAppointmentRequest":{"allOf":[{"$ref":"#/components/schemas/AppointmentSlotRequest"},{"type":"object","required":["patient_name","patient_number"],"properties":{"patient_name":{"type":"string","minLength":1,"maxLength":120},"patient_number":{"type":"string","pattern":"^\\+[1-9]\\d{7,14}$"}}}]},"AppointmentCheckRequest":{"type":"object","required":["date","doctor_id"],"not":{"required":["payload"]},"oneOf":[{"required":["time"]},{"required":["time_window"]}],"properties":{"date":{"type":"string","format":"date","maxLength":10},"time":{"$ref":"#/components/schemas/AppointmentTime"},"time_window":{"type":"string","enum":["morning","afternoon","evening"],"description":"Classifies slot start times in the clinic timezone: morning before 12:00 PM, afternoon from 12:00 PM to before 03:00 PM, evening from 03:00 PM. Slots must fit the day's working periods with their buffers. Only the requested date and window are searched."},"doctor_id":{"type":"string","minLength":1,"maxLength":128,"description":"The id of a doctor in the selected clinic's doctors array, not the Google Calendar ID."}}},"AppointmentSlotRequest":{"type":"object","required":["date","time","doctor_id"],"properties":{"date":{"type":"string","format":"date","maxLength":10},"time":{"$ref":"#/components/schemas/AppointmentTime"},"doctor_id":{"type":"string","minLength":1,"maxLength":128,"description":"The id of a doctor in the selected clinic's doctors array, not the Google Calendar ID."}}},"AppointmentTime":{"type":"string","pattern":"^(?:0?[1-9]|1[0-2]):[0-5]\\d [aApP][mM]$","example":"11:30 AM"},"AppointmentAlternatives":{"type":"array","maxItems":2,"description":"Chronological free grid slots. Exact-time requests select the two nearest starts, with earlier starts winning ties and later-day fallback when necessary. Time-window requests select the first two starts in the requested same-day window with no fallback. May contain zero or one slot. Past slots are excluded.","items":{"type":"object","additionalProperties":false,"required":["date","time"],"properties":{"date":{"type":"string","format":"date"},"time":{"$ref":"#/components/schemas/AppointmentTime"}}}},"UnavailableAppointment":{"type":"object","additionalProperties":false,"required":["status","alternatives"],"properties":{"status":{"type":"string","enum":["unavailable"]},"alternatives":{"$ref":"#/components/schemas/AppointmentAlternatives"}}},"AppointmentError":{"type":"object","required":["status","message"],"properties":{"status":{"type":"string","enum":["error"]},"message":{"type":"string"}}}}}}