{
  "openapi": "3.1.0",
  "info": {
    "title": "WERZ Agent Booking API",
    "version": "1.0.0",
    "description": "Server-to-server booking and self-serve discovery endpoints for outside AI agents. Requires an admin-issued agent API key with the bookings scope. Retries for the same confirmed booking identity return the existing reservation with idempotent=true."
  },
  "externalDocs": {
    "description": "Human-readable docs and admin provisioning guide",
    "url": "https://werz.ai/resources/agent-booking"
  },
  "servers": [
    {
      "url": "https://werz.ai"
    }
  ],
  "components": {
    "securitySchemes": {
      "agentApiKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "Use an admin-issued bearer token that starts with wrzk_. Outside booking agents should use the bookings scope. Internal-only booking management automations can use bookings-admin."
      }
    },
    "headers": {
      "X-RateLimit-Limit": {
        "description": "Maximum requests allowed in the current one-minute rate-limit window.",
        "schema": { "type": "integer" }
      },
      "X-RateLimit-Remaining": {
        "description": "Requests remaining in the current one-minute rate-limit window.",
        "schema": { "type": "integer" }
      },
      "X-RateLimit-Reset": {
        "description": "Unix timestamp in milliseconds when the current rate-limit window resets.",
        "schema": { "type": "integer" }
      },
      "Retry-After": {
        "description": "Seconds until the current rate-limit window resets.",
        "schema": { "type": "integer" }
      }
    },
    "schemas": {
      "AvailabilityResponse": {
        "type": "object",
        "properties": {
          "availability": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "date": { "type": "string", "format": "date" },
                "slots": {
                  "type": "array",
                  "items": {
                    "type": "object",
                    "properties": {
                      "startTime": { "type": "string", "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$" },
                      "endTime": { "type": "string", "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$" },
                      "available": { "type": "boolean" }
                    },
                    "required": ["startTime", "endTime", "available"]
                  }
                }
              },
              "required": ["date", "slots"]
            }
          },
          "timezone": { "type": "string", "example": "America/Los_Angeles" },
          "slotDuration": { "type": "integer", "example": 30 }
        },
        "required": ["availability", "timezone", "slotDuration"]
      },
      "CapabilitiesResponse": {
        "type": "object",
        "properties": {
          "version": { "type": "string" },
          "lastUpdated": { "type": "string" },
          "access": {
            "type": "string",
            "enum": ["scoped", "full"]
          },
          "scopes": {
            "type": "array",
            "items": { "type": "string" }
          },
          "meta": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "method": { "type": "string" },
                "path": { "type": "string" },
                "description": { "type": "string" }
              },
              "required": ["method", "path", "description"]
            }
          },
          "endpoints": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "method": { "type": "string" },
                "path": { "type": "string" },
                "scope": { "type": "string" },
                "description": { "type": "string" },
                "params": { "type": "object" },
                "body": { "type": "object" }
              },
              "required": ["method", "path", "description"]
            }
          }
        },
        "required": ["version", "lastUpdated", "access", "scopes", "meta", "endpoints"]
      },
      "BookingRequest": {
        "type": "object",
        "properties": {
          "name": { "type": "string", "minLength": 2 },
          "email": { "type": "string", "format": "email" },
          "phone": {
            "type": "string",
            "description": "Required callback number with 10-15 digits after normalization."
          },
          "company": { "type": "string" },
          "date": { "type": "string", "format": "date" },
          "startTime": { "type": "string", "pattern": "^([01]\\d|2[0-3]):[0-5]\\d$" },
          "service": {
            "type": "string",
            "enum": ["VIDEO_PRODUCTION", "WEB_DEVELOPMENT", "MARKETING", "FULL_SERVICE"]
          },
          "budget": {
            "type": "string",
            "enum": ["UNDER_5K", "5K_10K", "10K_25K", "25K_50K", "50K_PLUS", "NOT_SURE"]
          },
          "notes": { "type": "string", "maxLength": 2000 },
          "source": {
            "type": "string",
            "description": "Optional caller label such as chatgpt, claude, gemini, or a workflow name. If omitted, analytics fall back to a stable agent-key prefix."
          }
        },
        "required": ["name", "email", "phone", "date", "startTime"]
      },
      "BookingResponse": {
        "type": "object",
        "properties": {
          "success": { "type": "boolean", "const": true },
          "idempotent": {
            "type": "boolean",
            "description": "True when the request matched an existing confirmed booking and the API returned that reservation instead of creating a duplicate."
          },
          "booking": {
            "type": "object",
            "properties": {
              "id": { "type": "string" },
              "date": { "type": "string", "format": "date" },
              "startTime": { "type": "string" },
              "endTime": { "type": "string" },
              "formattedDate": { "type": "string" },
              "formattedTime": { "type": "string" },
              "timezone": { "type": "string", "example": "America/Los_Angeles" },
              "status": { "type": "string", "example": "CONFIRMED" },
              "cancelUrl": { "type": "string", "format": "uri" },
              "meetUrl": { "type": ["string", "null"], "format": "uri" }
            },
            "required": [
              "id",
              "date",
              "startTime",
              "endTime",
              "formattedDate",
              "formattedTime",
              "timezone",
              "status",
              "cancelUrl",
              "meetUrl"
            ]
          },
          "lead": {
            "type": "object",
            "properties": {
              "id": { "type": ["string", "null"] },
              "isNew": { "type": "boolean" }
            },
            "required": ["id", "isNew"]
          },
          "source": {
            "type": "object",
            "properties": {
              "value": { "type": "string" },
              "label": { "type": "string" }
            },
            "required": ["value", "label"]
          },
          "message": { "type": "string" }
        },
        "required": ["success", "idempotent", "booking", "lead", "source", "message"]
      },
      "RateLimitResponse": {
        "type": "object",
        "properties": {
          "limit": { "type": "integer" },
          "remaining": { "type": "integer" },
          "resetAt": { "type": "integer" },
          "resetIn": { "type": "integer" }
        },
        "required": ["limit", "remaining", "resetAt", "resetIn"]
      },
      "RateLimitExceededResponse": {
        "type": "object",
        "properties": {
          "error": { "type": "string", "example": "Rate limit exceeded" },
          "retryAfter": { "type": "integer", "description": "Seconds to wait before retrying." }
        },
        "required": ["error", "retryAfter"]
      },
      "ErrorResponse": {
        "type": "object",
        "properties": {
          "error": { "type": "string" },
          "code": { "type": "string" },
          "details": {}
        },
        "required": ["error"]
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "Missing or invalid agent API key",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/ErrorResponse" }
          }
        }
      },
      "Forbidden": {
        "description": "The current key does not have the required scope or role for this route.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/ErrorResponse" }
          }
        }
      },
      "RateLimited": {
        "description": "Rate limit exceeded. Respect the Retry-After header before retrying.",
        "headers": {
          "Retry-After": {
            "$ref": "#/components/headers/Retry-After"
          }
        },
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/RateLimitExceededResponse" }
          }
        }
      }
    }
  },
  "security": [
    {
      "agentApiKey": []
    }
  ],
  "paths": {
    "/api/agent/capabilities": {
      "get": {
        "summary": "Discover the routes available to the current key",
        "description": "Returns the manifest available to the current caller. Scoped agent keys only receive the subset of endpoints they can actually use.",
        "responses": {
          "200": {
            "description": "Capabilities manifest",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/CapabilitiesResponse" }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/api/agent/rate-limit": {
      "get": {
        "summary": "Inspect the current rate-limit budget",
        "description": "Returns the current remaining quota and reset timing for the calling agent API key.",
        "responses": {
          "200": {
            "description": "Rate-limit state",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/RateLimitResponse" }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/api/agent/booking/availability": {
      "get": {
        "summary": "List open booking slots",
        "description": "Returns available 30-minute consultation slots. Queries are limited to a 30-day range and the public 60-day booking window.",
        "parameters": [
          {
            "name": "start",
            "in": "query",
            "required": false,
            "schema": { "type": "string", "format": "date" }
          },
          {
            "name": "end",
            "in": "query",
            "required": false,
            "schema": { "type": "string", "format": "date" }
          }
        ],
        "responses": {
          "200": {
            "description": "Availability payload",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/AvailabilityResponse" }
              }
            }
          },
          "400": {
            "description": "Invalid date range",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/api/agent/booking": {
      "post": {
        "summary": "Create a confirmed booking",
        "description": "Creates a confirmed WERZ consultation booking, sends confirmation/admin email notifications, and returns cancel/meeting URLs when available. If the same confirmed booking already exists for the same email, date, and start time, the API returns that reservation with idempotent=true.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/BookingRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Booking created",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/BookingResponse" }
              }
            }
          },
          "400": {
            "description": "Validation or booking window error",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "409": {
            "description": "Slot was taken or day is fully booked",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" }
              }
            }
          }
        }
      }
    }
  }
}
