{
  "openapi": "3.1.0",
  "info": {
    "title": "ThePetsMonk — book a pet grooming appointment",
    "version": "1.0.0",
    "description": "Lets an AI assistant request a grooming appointment at a salon that uses ThePetsMonk, on behalf of the pet's owner. No authentication. Every request is sent to the salon to confirm (it is not an instant booking); the owner gets a text when the salon confirms. Typical flow: GET the salon card (note requiredPetDetails), GET /services, GET /slots, POST /requests with dryRun=true to show the owner the price, then POST /requests again with dryRun=false. The salon's slug is the last path segment of its page, e.g. thepetsmonk.com/groomers/austin-tx/happy-paws → happy-paws."
  },
  "servers": [{ "url": "https://api.thepetsmonk.com" }],
  "paths": {
    "/api/public/agent/{orgSlug}": {
      "get": {
        "operationId": "getSalon",
        "summary": "Salon card: name, contact, currency, weight unit and the pet details this salon requires",
        "parameters": [{ "$ref": "#/components/parameters/orgSlug" }],
        "responses": {
          "200": { "description": "Salon card", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Salon" } } } },
          "403": { "$ref": "#/components/responses/Error" },
          "404": { "$ref": "#/components/responses/Error" }
        }
      }
    },
    "/api/public/agent/{orgSlug}/services": {
      "get": {
        "operationId": "listServices",
        "summary": "Services the salon offers. priceFrom is a starting price; the exact price depends on the pet (use a dryRun request).",
        "parameters": [{ "$ref": "#/components/parameters/orgSlug" }],
        "responses": {
          "200": { "description": "Services", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/Service" } } } } }
        }
      }
    },
    "/api/public/agent/{orgSlug}/slots": {
      "get": {
        "operationId": "openTimes",
        "summary": "Open start times (HH:mm, salon's local time) for a service on a date, for a pet of this breed and weight",
        "parameters": [
          { "$ref": "#/components/parameters/orgSlug" },
          { "name": "service", "in": "query", "required": true, "description": "Service id or name", "schema": { "type": "string" } },
          { "name": "date", "in": "query", "required": true, "schema": { "type": "string", "format": "date" } },
          { "name": "petType", "in": "query", "schema": { "type": "string", "enum": ["DOG", "CAT", "BIRD", "RABBIT", "REPTILE", "OTHER"], "default": "DOG" } },
          { "name": "breed", "in": "query", "schema": { "type": "string" } },
          { "name": "weight", "in": "query", "schema": { "type": "number" } },
          { "name": "weightUnit", "in": "query", "description": "LBS or KG; defaults to the salon's unit", "schema": { "type": "string" } }
        ],
        "responses": {
          "200": { "description": "Open start times", "content": { "application/json": { "schema": { "type": "array", "items": { "type": "string", "example": "10:30" } } } } }
        }
      }
    },
    "/api/public/agent/{orgSlug}/requests": {
      "post": {
        "operationId": "requestBooking",
        "summary": "Request an appointment (sent to the salon to confirm). dryRun=true returns the price without sending.",
        "parameters": [
          { "$ref": "#/components/parameters/orgSlug" },
          { "name": "Idempotency-Key", "in": "header", "description": "Any unique string. Retrying with the same key returns the original request instead of creating another.", "schema": { "type": "string" } }
        ],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BookingRequest" } } } },
        "responses": {
          "200": { "description": "Quote (dryRun)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BookingResponse" } } } },
          "201": { "description": "Request sent to the salon", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BookingResponse" } } } },
          "400": { "$ref": "#/components/responses/Error" },
          "403": { "$ref": "#/components/responses/Error" },
          "404": { "$ref": "#/components/responses/Error" },
          "409": { "$ref": "#/components/responses/Error" },
          "422": { "$ref": "#/components/responses/Error" },
          "429": { "$ref": "#/components/responses/Error" }
        }
      }
    }
  },
  "components": {
    "parameters": {
      "orgSlug": { "name": "orgSlug", "in": "path", "required": true, "description": "The salon's slug", "schema": { "type": "string" } }
    },
    "responses": {
      "Error": {
        "description": "An error the assistant can act on",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      }
    },
    "schemas": {
      "Salon": {
        "type": "object",
        "properties": {
          "name": { "type": "string" },
          "phone": { "type": ["string", "null"] },
          "address": { "type": ["string", "null"] },
          "timezone": { "type": "string" },
          "currency": { "type": "string" },
          "weightUnit": { "type": "string" },
          "requiredPetDetails": {
            "type": "array",
            "description": "Ask the owner for all of these before requesting; send them in the pet object under the given field name.",
            "items": { "type": "object", "properties": { "field": { "type": "string" }, "label": { "type": "string" } } }
          },
          "howBookingWorks": { "type": "string" }
        }
      },
      "Service": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "name": { "type": "string" },
          "description": { "type": ["string", "null"] },
          "category": { "type": ["string", "null"] },
          "priceFrom": { "type": "number" },
          "durationMinutes": { "type": "integer" }
        }
      },
      "BookingRequest": {
        "type": "object",
        "required": ["service", "date", "time", "customer", "pet"],
        "properties": {
          "service": { "type": "string", "description": "Service id or name" },
          "date": { "type": "string", "format": "date" },
          "time": { "type": "string", "description": "HH:mm, salon's local time", "example": "10:00" },
          "customer": {
            "type": "object",
            "required": ["name", "phone"],
            "properties": {
              "name": { "type": "string" },
              "phone": { "type": "string", "description": "The owner's mobile; the salon texts the confirmation here" },
              "email": { "type": "string" }
            }
          },
          "pet": {
            "type": "object",
            "required": ["name"],
            "properties": {
              "name": { "type": "string" },
              "type": { "type": "string", "enum": ["DOG", "CAT", "BIRD", "RABBIT", "REPTILE", "OTHER"], "default": "DOG" },
              "breed": { "type": "string" },
              "weight": { "type": "number" },
              "weightUnit": { "type": "string", "description": "LBS or KG" },
              "behavioralNotes": { "type": "string" },
              "healthIssues": { "type": "string" },
              "specialInstructions": { "type": "string" },
              "isFirstGrooming": { "type": "boolean" },
              "isSpayedNeutered": { "type": "boolean" },
              "vetName": { "type": "string" },
              "vetPhone": { "type": "string" }
            }
          },
          "notes": { "type": "string", "description": "Anything the owner wants the salon to know" },
          "agent": { "type": "object", "properties": { "name": { "type": "string", "description": "Your assistant's name, shown to the salon", "example": "ChatGPT" } } },
          "dryRun": { "type": "boolean", "default": false, "description": "true = price and checks only, nothing is sent" }
        }
      },
      "BookingResponse": {
        "type": "object",
        "properties": {
          "status": { "type": "string", "enum": ["QUOTE", "REQUESTED"] },
          "requestId": { "type": "string" },
          "service": { "type": "string" },
          "date": { "type": "string", "format": "date" },
          "time": { "type": "string" },
          "price": { "type": "number" },
          "currency": { "type": "string" },
          "durationMinutes": { "type": "integer" },
          "message": { "type": "string", "description": "Tell the owner this" }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "code": {
            "type": "string",
            "enum": ["INVALID_REQUEST", "SALON_NOT_FOUND", "AGENT_BOOKING_DISABLED", "SERVICE_NOT_FOUND", "SERVICE_AMBIGUOUS", "SLOT_TAKEN", "IN_THE_PAST", "INVALID_PHONE", "INVALID_PET_TYPE", "PET_DETAILS_REQUIRED", "VACCINATION_REQUIRED", "OUTSIDE_SERVICE_AREA", "NOT_ACCEPTED_ONLINE", "LIMIT_REACHED", "RATE_LIMITED", "REQUEST_FAILED"],
            "description": "SLOT_TAKEN → offer `alternatives`. SERVICE_AMBIGUOUS/NOT_FOUND → choose from `options`. PET_DETAILS_REQUIRED → ask for `fields`. AGENT_BOOKING_DISABLED/LIMIT_REACHED/NOT_ACCEPTED_ONLINE → suggest contacting the salon directly."
          },
          "message": { "type": "string" },
          "alternatives": { "type": "array", "items": { "type": "string" } },
          "options": { "type": "array", "items": { "type": "string" } },
          "fields": { "type": "array", "items": { "type": "object", "properties": { "field": { "type": "string" }, "label": { "type": "string" } } } },
          "problems": { "type": "array", "items": { "type": "string" } },
          "retryAfterSeconds": { "type": "integer" }
        }
      }
    }
  }
}
