{
  "openapi": "3.1.0",
  "info": {
    "title": "Book a call with Isa Ozler",
    "version": "1.0.0",
    "description": "Find Isa Ozler's free times and request a call of 5, 15, 30 or 60 minutes, online (Google Meet) or in person in Amsterdam. Isa confirms every request personally; Google Calendar then emails the invite. No authentication. Docs: https://isaozler.com/agents/",
    "contact": { "name": "Isa Ozler", "url": "https://isaozler.com/agents/" }
  },
  "servers": [{ "url": "https://isaozler.com" }],
  "externalDocs": { "url": "https://isaozler.com/agents/" },
  "paths": {
    "/api/agent/availability/": {
      "get": {
        "operationId": "findFreeTimes",
        "summary": "Isa's free times",
        "description": "Free stretches in Isa's calendar for a call of the given length, on weekdays within the hours Isa opened for calls. At most 31 days per lookup, within the next 90 days. Defaults to the next 14 days.",
        "parameters": [
          {
            "name": "format",
            "in": "query",
            "required": true,
            "description": "Response format. Use json.",
            "schema": { "type": "string", "enum": ["json"], "default": "json" }
          },
          {
            "name": "from",
            "in": "query",
            "description": "Start of the lookup, ISO 8601. Defaults to now.",
            "schema": { "type": "string", "format": "date-time" }
          },
          {
            "name": "to",
            "in": "query",
            "description": "End of the lookup, ISO 8601. Defaults to 14 days from now.",
            "schema": { "type": "string", "format": "date-time" }
          },
          {
            "name": "duration",
            "in": "query",
            "description": "Call length in minutes.",
            "schema": { "type": "integer", "enum": [5, 15, 30, 60], "default": 15 }
          },
          {
            "name": "tz",
            "in": "query",
            "description": "IANA time zone for the `local` labels, e.g. Europe/London. Defaults to Europe/Amsterdam.",
            "schema": { "type": "string" }
          }
        ],
        "responses": {
          "200": {
            "description": "Free windows",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/FreeTimes" } } }
          },
          "400": {
            "description": "Invalid range or parameters",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          }
        }
      }
    },
    "/api/agent/request/": {
      "post": {
        "operationId": "requestCall",
        "summary": "Request a call",
        "description": "Sends Isa a call request for a window taken from findFreeTimes. Isa is notified, books a time inside the window, and Google Calendar emails the invite to `email`. Stores the details given. One open request per email address.",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CallRequest" } } }
        },
        "responses": {
          "200": {
            "description": "Request sent",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RequestCreated" } } }
          },
          "400": {
            "description": "Not sent; `detail` says why (window outside open hours, open request for this email, invalid field)",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          }
        }
      }
    },
    "/api/agent/status/": {
      "post": {
        "operationId": "getCallRequest",
        "summary": "Status of a request",
        "description": "Whether a request is waiting for Isa, booked (with time, Meet link and calendar file), not scheduled, or cancelled.",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RequestRef" } } }
        },
        "responses": {
          "200": {
            "description": "Current status",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RequestStatus" } } }
          },
          "404": {
            "description": "No request for that requestId and accessToken",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          }
        }
      }
    },
    "/api/agent/cancel/": {
      "post": {
        "operationId": "cancelCallRequest",
        "summary": "Withdraw a request",
        "description": "Withdraws a request that isn't booked yet. A booked call is cancelled by declining the calendar invite.",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RequestRef" } } }
        },
        "responses": {
          "200": {
            "description": "Status after withdrawing",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RequestStatus" } } }
          },
          "400": {
            "description": "Not withdrawn (already booked, or not found)",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Window": {
        "type": "object",
        "properties": {
          "start": { "type": "string", "format": "date-time" },
          "end": { "type": "string", "format": "date-time" },
          "local": { "type": "string", "description": "Readable label in the requested time zone" }
        },
        "required": ["start", "end", "local"]
      },
      "FreeTimes": {
        "type": "object",
        "properties": {
          "timeZone": { "type": "string" },
          "durationMinutes": { "type": "integer" },
          "calendarChecked": {
            "type": "boolean",
            "description": "False when Isa's calendar couldn't be read; the windows are then the bookable hours only"
          },
          "freeWindows": { "type": "array", "items": { "$ref": "#/components/schemas/Window" } },
          "note": { "type": "string" },
          "requestLinkExample": {
            "type": "string",
            "description": "A prefilled /book/confirm/ link a person can open to send a request themselves"
          }
        },
        "required": ["timeZone", "durationMinutes", "freeWindows"]
      },
      "CallRequest": {
        "type": "object",
        "properties": {
          "from": {
            "type": "string",
            "format": "date-time",
            "description": "Window start, ISO 8601 (a free window from findFreeTimes, or part of one)"
          },
          "to": { "type": "string", "format": "date-time", "description": "Window end, ISO 8601" },
          "duration": { "type": "integer", "enum": [5, 15, 30, 60], "default": 15 },
          "start": {
            "type": "string",
            "format": "date-time",
            "description": "Preferred start inside the window (optional)"
          },
          "name": { "type": "string", "maxLength": 120, "description": "Name of the person who wants the call" },
          "email": { "type": "string", "format": "email", "description": "Where Google Calendar sends the invite" },
          "company": { "type": "string", "maxLength": 160 },
          "topics": {
            "type": "array",
            "items": { "type": "string", "enum": ["leadership", "brand", "product", "engineering", "other"] },
            "description": "leadership = Product & design leadership, brand = Brand & typography, product = Product design, engineering = Design engineering"
          },
          "message": { "type": "string", "maxLength": 1000, "description": "What the call is about" },
          "mode": { "type": "string", "enum": ["online", "in-person"], "default": "online" },
          "tz": { "type": "string", "description": "The person's IANA time zone" },
          "agent": {
            "type": "string",
            "maxLength": 80,
            "description": "Name of the assistant sending the request, shown to Isa"
          }
        },
        "required": ["from", "to", "name", "email"]
      },
      "RequestCreated": {
        "type": "object",
        "properties": {
          "requestId": { "type": "string" },
          "accessToken": { "type": "string", "description": "Grants access to this one request" },
          "status": { "type": "string", "enum": ["waiting_for_isa"] },
          "message": { "type": "string" }
        },
        "required": ["requestId", "accessToken", "status"]
      },
      "RequestRef": {
        "type": "object",
        "properties": {
          "requestId": { "type": "string" },
          "accessToken": { "type": "string" }
        },
        "required": ["requestId", "accessToken"]
      },
      "RequestStatus": {
        "type": "object",
        "properties": {
          "requestId": { "type": "string" },
          "status": { "type": "string", "enum": ["waiting_for_isa", "booked", "not_scheduled", "cancelled"] },
          "statusText": { "type": "string" },
          "durationMinutes": { "type": "integer" },
          "mode": { "type": "string" },
          "window": {
            "type": "object",
            "properties": { "from": { "type": "string" }, "to": { "type": "string" } }
          },
          "preferredStart": { "type": "string" },
          "booked": { "$ref": "#/components/schemas/Window" },
          "meetUrl": { "type": "string" },
          "inviteSentTo": { "type": "string" },
          "icsUrl": { "type": "string" }
        },
        "required": ["requestId", "status", "statusText"]
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": { "type": "string" },
          "detail": { "type": "string" }
        },
        "required": ["error"]
      }
    }
  }
}
