{
  "openapi": "3.1.0",
  "info": {
    "title": "WHC Consultation Booking Gateway API",
    "version": "1.0.0",
    "summary": "OAuth-protected, consultation-only WHC booking REST API.",
    "description": "Public clients must discover enabled options dynamically rather than hard-code\nconsultation names or identifiers. The gateway never exposes treatment calendars,\nprovider identifiers, or private mappings. Booking creation additionally depends\non an independent server-side write switch, which is currently disabled.\nThis API does not provide medical advice, diagnosis, triage, or emergency help.\n"
  },
  "servers": [
    {
      "url": "https://agent-booking.thewomenshealth.clinic",
      "description": "Canonical WHC Booking Gateway"
    }
  ],
  "security": [
    {
      "oauth2": [
        "whc.booking.read"
      ]
    }
  ],
  "tags": [
    {
      "name": "Consultations"
    },
    {
      "name": "Bookings"
    }
  ],
  "paths": {
    "/agent/v1/consultations": {
      "get": {
        "operationId": "listConsultations",
        "summary": "List currently enabled consultation options",
        "tags": [
          "Consultations"
        ],
        "security": [
          {
            "oauth2": [
              "whc.booking.read"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "Enabled approved consultation calendars, using public fields only.",
            "headers": {
              "X-Correlation-ID": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "minItems": 0,
                      "items": {
                        "$ref": "#/components/schemas/Consultation"
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/GatewayMeta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/agent/v1/consultations/{id}/slots": {
      "get": {
        "operationId": "listConsultationSlots",
        "summary": "List available slots for an approved consultation",
        "tags": [
          "Consultations"
        ],
        "security": [
          {
            "oauth2": [
              "whc.booking.read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/ConsultationId"
          },
          {
            "$ref": "#/components/parameters/SlotLimit"
          }
        ],
        "responses": {
          "200": {
            "description": "Normalized mock or live slots with gateway-issued references.",
            "headers": {
              "X-Correlation-ID": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": false,
                  "required": [
                    "data",
                    "meta"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "additionalProperties": false,
                      "required": [
                        "consultation_id",
                        "slots"
                      ],
                      "properties": {
                        "consultation_id": {
                          "$ref": "#/components/schemas/ConsultationId"
                        },
                        "slots": {
                          "type": "array",
                          "maxItems": 100,
                          "items": {
                            "$ref": "#/components/schemas/Slot"
                          }
                        }
                      }
                    },
                    "meta": {
                      "$ref": "#/components/schemas/GatewayMeta"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "502": {
            "$ref": "#/components/responses/ProviderError"
          },
          "503": {
            "$ref": "#/components/responses/ProviderError"
          }
        }
      }
    },
    "/agent/v1/bookings": {
      "post": {
        "operationId": "createConsultationBooking",
        "summary": "Create a consultation booking when gateway writes are enabled",
        "tags": [
          "Bookings"
        ],
        "description": "Requires explicit user confirmation and booking-processing consent. OAuth\nwrite scope does not override the independent booking-write switch. The\ncurrent public policy keeps booking writes disabled.\n",
        "security": [
          {
            "oauth2": [
              "whc.booking.write"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateBookingRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Idempotent replay of the original gateway booking.",
            "headers": {
              "X-Correlation-ID": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BookingEnvelope"
                }
              }
            }
          },
          "201": {
            "description": "A new gateway booking was created.",
            "headers": {
              "X-Correlation-ID": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BookingEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/LiveBookingDisabled"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "410": {
            "$ref": "#/components/responses/Gone"
          },
          "413": {
            "$ref": "#/components/responses/RequestTooLarge"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "502": {
            "$ref": "#/components/responses/ProviderError"
          },
          "503": {
            "$ref": "#/components/responses/ProviderError"
          }
        }
      }
    },
    "/agent/v1/bookings/{id}": {
      "get": {
        "operationId": "getConsultationBooking",
        "summary": "Retrieve the caller-owned durable gateway booking record",
        "tags": [
          "Bookings"
        ],
        "security": [
          {
            "oauth2": [
              "whc.booking.read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/BookingId"
          }
        ],
        "responses": {
          "200": {
            "description": "The caller-owned gateway booking record; this is not a live GHL status refresh.",
            "headers": {
              "X-Correlation-ID": {
                "$ref": "#/components/headers/CorrelationId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BookingEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "oauth2": {
        "type": "oauth2",
        "description": "Auth0 OAuth. Approved-client/tester policy and Firestore controls also apply.",
        "flows": {
          "authorizationCode": {
            "authorizationUrl": "https://whc-booking-dev.uk.auth0.com/authorize",
            "tokenUrl": "https://whc-booking-dev.uk.auth0.com/oauth/token",
            "scopes": {
              "whc.booking.read": "List consultations and slots and retrieve caller-owned booking records.",
              "whc.booking.write": "Request consultation booking creation when writes are enabled."
            }
          }
        }
      }
    },
    "headers": {
      "CorrelationId": {
        "description": "Non-sensitive identifier used to trace one gateway request.",
        "schema": {
          "type": "string",
          "minLength": 8,
          "maxLength": 100
        }
      }
    },
    "parameters": {
      "ConsultationId": {
        "name": "id",
        "in": "path",
        "required": true,
        "schema": {
          "$ref": "#/components/schemas/ConsultationId"
        }
      },
      "BookingId": {
        "name": "id",
        "in": "path",
        "required": true,
        "schema": {
          "$ref": "#/components/schemas/BookingId"
        }
      },
      "SlotLimit": {
        "name": "limit",
        "in": "query",
        "required": false,
        "description": "Maximum number of normalized slots to return.",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 100,
          "default": 20
        }
      },
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": true,
        "description": "URL-safe key for one intended booking mutation.",
        "schema": {
          "type": "string",
          "minLength": 16,
          "maxLength": 128,
          "pattern": "^[A-Za-z0-9._~-]+$"
        }
      }
    },
    "schemas": {
      "ConsultationId": {
        "type": "string",
        "minLength": 3,
        "maxLength": 80,
        "pattern": "^[a-z][a-z0-9_]{2,79}$",
        "description": "Public alias returned dynamically by the consultations operation."
      },
      "Consultation": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "id",
          "name",
          "description",
          "duration_minutes"
        ],
        "properties": {
          "id": {
            "$ref": "#/components/schemas/ConsultationId"
          },
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100
          },
          "description": {
            "type": "string",
            "minLength": 1,
            "maxLength": 500
          },
          "duration_minutes": {
            "type": "integer",
            "minimum": 1,
            "maximum": 480
          }
        }
      },
      "SlotId": {
        "type": "string",
        "minLength": 8,
        "maxLength": 512,
        "pattern": "^(slot_[A-Za-z0-9_-]+|st1\\.[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+)$"
      },
      "Slot": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "id",
          "consultation_id",
          "starts_at",
          "ends_at",
          "timezone"
        ],
        "properties": {
          "id": {
            "$ref": "#/components/schemas/SlotId"
          },
          "consultation_id": {
            "$ref": "#/components/schemas/ConsultationId"
          },
          "starts_at": {
            "type": "string",
            "format": "date-time",
            "description": "Europe/London timestamp with an explicit UTC or British Summer Time offset."
          },
          "ends_at": {
            "type": "string",
            "format": "date-time",
            "description": "Later Europe/London timestamp using the same explicit-offset format."
          },
          "timezone": {
            "type": "string",
            "const": "Europe/London"
          }
        }
      },
      "Contact": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "first_name",
          "last_name",
          "email",
          "mobile"
        ],
        "properties": {
          "first_name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100
          },
          "last_name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100
          },
          "email": {
            "type": "string",
            "format": "email",
            "maxLength": 254
          },
          "mobile": {
            "type": "string",
            "minLength": 7,
            "maxLength": 32
          }
        }
      },
      "Consent": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "booking_processing"
        ],
        "properties": {
          "booking_processing": {
            "type": "boolean",
            "const": true
          }
        }
      },
      "CreateBookingRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "consultation_id",
          "slot_id",
          "contact",
          "consent"
        ],
        "properties": {
          "consultation_id": {
            "$ref": "#/components/schemas/ConsultationId"
          },
          "slot_id": {
            "$ref": "#/components/schemas/SlotId"
          },
          "contact": {
            "$ref": "#/components/schemas/Contact"
          },
          "consent": {
            "$ref": "#/components/schemas/Consent"
          },
          "source_channel": {
            "type": "string",
            "enum": [
              "local_test",
              "ai_agent"
            ],
            "default": "ai_agent"
          }
        }
      },
      "BookingId": {
        "type": "string",
        "minLength": 16,
        "maxLength": 200,
        "pattern": "^book_(mock_[0-9]{6}|live_[A-Za-z0-9_-]+|sec_[A-Za-z0-9_-]+)$"
      },
      "Booking": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "id",
          "consultation_id",
          "slot_id",
          "status",
          "starts_at",
          "ends_at",
          "timezone",
          "created_at"
        ],
        "properties": {
          "id": {
            "$ref": "#/components/schemas/BookingId"
          },
          "consultation_id": {
            "$ref": "#/components/schemas/ConsultationId"
          },
          "slot_id": {
            "$ref": "#/components/schemas/SlotId"
          },
          "status": {
            "type": "string",
            "enum": [
              "mock_confirmed",
              "confirmed",
              "action_required"
            ]
          },
          "starts_at": {
            "type": "string",
            "format": "date-time"
          },
          "ends_at": {
            "type": "string",
            "format": "date-time"
          },
          "timezone": {
            "type": "string",
            "const": "Europe/London"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "status_source": {
            "type": "string",
            "const": "gateway_record"
          },
          "source": {
            "type": "string",
            "const": "whc_agent_booking_gateway"
          },
          "source_channel": {
            "type": "string",
            "enum": [
              "local_test",
              "ai_agent"
            ]
          }
        }
      },
      "GatewayMeta": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "mode"
        ],
        "properties": {
          "mode": {
            "type": "string",
            "enum": [
              "mock",
              "ghl_mcp_live"
            ]
          }
        }
      },
      "BookingEnvelope": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "data",
          "meta"
        ],
        "properties": {
          "data": {
            "$ref": "#/components/schemas/Booking"
          },
          "meta": {
            "$ref": "#/components/schemas/GatewayMeta"
          }
        }
      },
      "ErrorDetail": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "field",
          "reason"
        ],
        "properties": {
          "field": {
            "type": "string",
            "maxLength": 200
          },
          "reason": {
            "type": "string",
            "maxLength": 300
          }
        }
      },
      "Error": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "code",
          "message",
          "retryable",
          "correlation_id"
        ],
        "properties": {
          "code": {
            "type": "string",
            "pattern": "^[A-Z][A-Z0-9_]+$"
          },
          "message": {
            "type": "string",
            "maxLength": 500
          },
          "retryable": {
            "type": "boolean"
          },
          "correlation_id": {
            "type": "string",
            "minLength": 8,
            "maxLength": 100
          },
          "details": {
            "type": "array",
            "maxItems": 20,
            "items": {
              "$ref": "#/components/schemas/ErrorDetail"
            }
          }
        }
      },
      "ErrorEnvelope": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "$ref": "#/components/schemas/Error"
          }
        }
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "OAuth authentication with the operation's required scope is required.",
        "headers": {
          "X-Correlation-ID": {
            "$ref": "#/components/headers/CorrelationId"
          },
          "WWW-Authenticate": {
            "description": "Bearer challenge referencing protected-resource metadata.",
            "schema": {
              "type": "string"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "BadRequest": {
        "description": "Request validation failed.",
        "headers": {
          "X-Correlation-ID": {
            "$ref": "#/components/headers/CorrelationId"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "LiveBookingDisabled": {
        "description": "Live booking writes were not explicitly enabled at startup.",
        "headers": {
          "X-Correlation-ID": {
            "$ref": "#/components/headers/CorrelationId"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "NotFound": {
        "description": "The endpoint or allowlisted gateway resource was not found.",
        "headers": {
          "X-Correlation-ID": {
            "$ref": "#/components/headers/CorrelationId"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "Conflict": {
        "description": "The slot, idempotency key, or durable booking state conflicts with the request.",
        "headers": {
          "X-Correlation-ID": {
            "$ref": "#/components/headers/CorrelationId"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "Gone": {
        "description": "The signed consultation slot token has expired.",
        "headers": {
          "X-Correlation-ID": {
            "$ref": "#/components/headers/CorrelationId"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "RequestTooLarge": {
        "description": "The request body exceeds 32 KiB.",
        "headers": {
          "X-Correlation-ID": {
            "$ref": "#/components/headers/CorrelationId"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "UnsupportedMediaType": {
        "description": "Content-Type is not application/json.",
        "headers": {
          "X-Correlation-ID": {
            "$ref": "#/components/headers/CorrelationId"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "UnprocessableEntity": {
        "description": "Required booking-processing consent was not asserted.",
        "headers": {
          "X-Correlation-ID": {
            "$ref": "#/components/headers/CorrelationId"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "InternalError": {
        "description": "Unexpected non-disclosing gateway error.",
        "headers": {
          "X-Correlation-ID": {
            "$ref": "#/components/headers/CorrelationId"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "ProviderError": {
        "description": "HighLevel MCP rejected, could not resolve, or could not complete the operation.",
        "headers": {
          "X-Correlation-ID": {
            "$ref": "#/components/headers/CorrelationId"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      }
    }
  }
}
