{
  "openapi": "3.1.0",
  "info": {
    "title": "eKard Agent API",
    "version": "1",
    "summary": "Operate a eKard account over HTTP: cards, contacts, documents, QR codes, designs, analytics, organizations, and Calendar's bookings, schedule, clients and takings.",
    "description": "REST JSON API for AI agents acting on an account owner's behalf: everything the owner can do in the web app, over HTTP.\n\n- **Key:** the owner finds the account's API key at https://ekard.app/account/ai and sends it on every request as `Authorization: Bearer <key>`. One account, one key, full account power; rotating it there stops the old key at once.\n- **Responses** are JSON wrapped in a resource key (`{\"kard\": {...}}`). **Errors** are `{\"error\": {\"code\", \"message\", \"details\"}}` with the right HTTP status; `404` means the record doesn't exist or isn't this account's, `403` that it exists but the account's role can't do that.\n- **Updates are partial:** omitted fields keep their value; send `\"\"` to clear one. `PUT` is accepted wherever `PATCH` is, with the same meaning.\n- **Kards are addressed by `share_link`** (their public URL slug). If the same share_link exists in two of your organizations, add `?organization=<org-slug>`.\n- The prose reference, with examples and advice for agents: https://ekard.app/api/v1/docs\n",
    "termsOfService": "https://ekard.app/pages/terms_and_conditions",
    "contact": {
      "name": "eKard support",
      "url": "https://ekard.app/pages/help"
    }
  },
  "externalDocs": {
    "description": "The full reference as plain markdown",
    "url": "https://ekard.app/api/v1/docs"
  },
  "servers": [
    {
      "url": "https://ekard.app/api/v1"
    }
  ],
  "security": [
    {
      "apiKey": []
    }
  ],
  "tags": [
    {
      "name": "Docs",
      "description": "These descriptions. Public, no key needed."
    },
    {
      "name": "Account",
      "description": "The account behind the key, and its plan."
    },
    {
      "name": "Kards",
      "description": "Digital business cards, addressed by share_link."
    },
    {
      "name": "Analytics",
      "description": "Visits, devices, sources and contacts, per kard and per account."
    },
    {
      "name": "Contacts",
      "description": "People who sent their details through a kard (leads)."
    },
    {
      "name": "Events",
      "description": "Named lead-capture buckets, such as a trade fair; contacts can carry an event_id."
    },
    {
      "name": "Documents",
      "description": "Files on a kard, each with a stable public URL and QR code, and an optional custom landing page."
    },
    {
      "name": "QR code",
      "description": "The kard's QR code design, logo and image."
    },
    {
      "name": "Custom design",
      "description": "The kard's own HTML page, with drafts, publishing, history, checkpoints and templates."
    },
    {
      "name": "Organizations",
      "description": "Organizations where the account is an admin."
    },
    {
      "name": "Bookmarks",
      "description": "Other kards this account saved."
    },
    {
      "name": "Bookings",
      "description": "The booking calendars this account manages, and what can be booked from a kard."
    },
    {
      "name": "Calendar",
      "description": "The booking calendar behind the kards: appointments, meetings, clients and the front desk. A key does in Calendar exactly what its person can do on the web, on the same roles (admin, front desk, staff); a calendar it has no role on answers 404, like one that doesn't exist."
    },
    {
      "name": "Calendar setup",
      "description": "Set a calendar up and keep it right: the kind of business and its settings, the menu, the team with their hours and time off, single-use booking links, who signs in, webhooks, importing a price list or clients, and syncing with Google Calendar or Outlook. Mostly for the calendar's admins."
    }
  ],
  "paths": {
    "/docs": {
      "get": {
        "operationId": "getReference",
        "tags": [
          "Docs"
        ],
        "summary": "The API reference as markdown",
        "security": [],
        "responses": {
          "200": {
            "description": "The reference, plain markdown.",
            "content": {
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/openapi.json": {
      "get": {
        "operationId": "getOpenApiDescription",
        "tags": [
          "Docs"
        ],
        "summary": "This OpenAPI 3.1 description",
        "security": [],
        "responses": {
          "200": {
            "description": "The description, as JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/me": {
      "get": {
        "operationId": "getAccount",
        "tags": [
          "Account"
        ],
        "summary": "The account, its plan and counts",
        "description": "Start here to confirm the key works.",
        "responses": {
          "200": {
            "description": "The account.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "account",
                    "subscription",
                    "counts",
                    "docs_url"
                  ],
                  "properties": {
                    "account": {
                      "$ref": "#/components/schemas/Account"
                    },
                    "subscription": {
                      "$ref": "#/components/schemas/Subscription"
                    },
                    "counts": {
                      "type": "object",
                      "required": [
                        "kards",
                        "contacts",
                        "organizations",
                        "bookmarks"
                      ],
                      "properties": {
                        "kards": {
                          "type": "integer"
                        },
                        "contacts": {
                          "type": "integer"
                        },
                        "organizations": {
                          "type": "integer"
                        },
                        "bookmarks": {
                          "type": "integer"
                        }
                      }
                    },
                    "docs_url": {
                      "type": "string",
                      "format": "uri"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/subscription": {
      "get": {
        "operationId": "getSubscription",
        "tags": [
          "Account"
        ],
        "summary": "The current plan",
        "description": "`subscription` is null on the free tier. Buying a plan happens on the web (Razorpay checkout): send the owner to `upgrade_url`.",
        "responses": {
          "200": {
            "description": "The plan.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "subscription",
                    "upgrade_url"
                  ],
                  "properties": {
                    "subscription": {
                      "$ref": "#/components/schemas/Subscription"
                    },
                    "upgrade_url": {
                      "type": "string",
                      "format": "uri"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "delete": {
        "operationId": "cancelSubscription",
        "tags": [
          "Account"
        ],
        "summary": "Cancel the plan at the end of the billing period",
        "description": "The same as the web's Cancel button. Confirm with the owner before calling this.",
        "responses": {
          "200": {
            "description": "Cancelled; it stays active until the period ends.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "cancelled",
                    "subscription"
                  ],
                  "properties": {
                    "cancelled": {
                      "const": true
                    },
                    "subscription": {
                      "$ref": "#/components/schemas/Subscription"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          }
        }
      }
    },
    "/analytics": {
      "get": {
        "operationId": "getAccountAnalytics",
        "tags": [
          "Analytics"
        ],
        "summary": "Visits and contacts for every kard the account owns",
        "description": "Use this for \"how are my kards doing?\".",
        "responses": {
          "200": {
            "description": "One row per kard, plus totals.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AccountAnalytics"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/kards": {
      "get": {
        "operationId": "listKards",
        "tags": [
          "Kards"
        ],
        "summary": "Every kard the account owns, newest first",
        "description": "Personal and organization-owned kards.",
        "responses": {
          "200": {
            "description": "The kards.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "kards"
                  ],
                  "properties": {
                    "kards": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/KardSummary"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "post": {
        "operationId": "createKard",
        "tags": [
          "Kards"
        ],
        "summary": "Create a kard",
        "description": "Only `name` is required. The `share_link` is generated; read it from the response. To attach a photo, send the whole request as multipart/form-data with fields named `kard[name]`, `kard[photo]` and so on.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "kard"
                ],
                "properties": {
                  "kard": {
                    "allOf": [
                      {
                        "$ref": "#/components/schemas/KardInput"
                      },
                      {
                        "required": [
                          "name"
                        ]
                      }
                    ]
                  }
                }
              }
            },
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/KardMultipart"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KardEnvelope"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          }
        }
      }
    },
    "/kards/{share_link}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ShareLink"
        },
        {
          "$ref": "#/components/parameters/Organization"
        }
      ],
      "get": {
        "operationId": "getKard",
        "tags": [
          "Kards"
        ],
        "summary": "One kard in full",
        "responses": {
          "200": {
            "description": "The kard.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KardEnvelope"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "operationId": "updateKard",
        "tags": [
          "Kards"
        ],
        "summary": "Update a kard",
        "description": "Partial: omitted fields keep their value. Custom links use nested semantics: no `id` adds a link, an `id` edits it, `\"_destroy\": true` removes it.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "kard"
                ],
                "properties": {
                  "kard": {
                    "$ref": "#/components/schemas/KardInput"
                  }
                }
              }
            },
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/KardMultipart"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated kard.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KardEnvelope"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          }
        }
      },
      "delete": {
        "operationId": "deleteKard",
        "tags": [
          "Kards"
        ],
        "summary": "Delete a kard and everything under it",
        "description": "Irreversible: confirm with the owner first. Organization kards can be deleted only by the organization's admins, and an organization's default kard not at all.",
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "deleted",
                    "share_link"
                  ],
                  "properties": {
                    "deleted": {
                      "const": true
                    },
                    "share_link": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/kards/{share_link}/analytics": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ShareLink"
        },
        {
          "$ref": "#/components/parameters/Organization"
        }
      ],
      "get": {
        "operationId": "getKardAnalytics",
        "tags": [
          "Analytics"
        ],
        "summary": "One kard's performance",
        "parameters": [
          {
            "name": "time_range",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "7_days",
                "30_days",
                "lifetime"
              ],
              "default": "7_days"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Visits, devices, sources, contacts and link clicks.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/KardAnalytics"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/contacts": {
      "get": {
        "operationId": "listAccountContacts",
        "tags": [
          "Contacts"
        ],
        "summary": "Contacts across all the account's kards, newest first",
        "parameters": [
          {
            "$ref": "#/components/parameters/Query"
          },
          {
            "$ref": "#/components/parameters/Page"
          },
          {
            "$ref": "#/components/parameters/PerPage"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of contacts.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContactPage"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/kards/{share_link}/contacts": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ShareLink"
        },
        {
          "$ref": "#/components/parameters/Organization"
        }
      ],
      "get": {
        "operationId": "listKardContacts",
        "tags": [
          "Contacts"
        ],
        "summary": "One kard's contacts",
        "parameters": [
          {
            "$ref": "#/components/parameters/Query"
          },
          {
            "$ref": "#/components/parameters/ContactSort"
          },
          {
            "$ref": "#/components/parameters/Direction"
          },
          {
            "$ref": "#/components/parameters/Page"
          },
          {
            "$ref": "#/components/parameters/PerPage"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of contacts.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContactPage"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "post": {
        "operationId": "createContact",
        "tags": [
          "Contacts"
        ],
        "summary": "Add a lead by hand",
        "description": "For a contact collected elsewhere; visitors use the kard's own connect form.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "contact"
                ],
                "properties": {
                  "contact": {
                    "allOf": [
                      {
                        "$ref": "#/components/schemas/ContactInput"
                      },
                      {
                        "required": [
                          "name"
                        ]
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContactEnvelope"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          }
        }
      }
    },
    "/kards/{share_link}/contacts.csv": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ShareLink"
        },
        {
          "$ref": "#/components/parameters/Organization"
        }
      ],
      "get": {
        "operationId": "exportKardContacts",
        "tags": [
          "Contacts"
        ],
        "summary": "One kard's contacts as a CSV file",
        "parameters": [
          {
            "$ref": "#/components/parameters/Query"
          },
          {
            "$ref": "#/components/parameters/ContactSort"
          },
          {
            "$ref": "#/components/parameters/Direction"
          }
        ],
        "responses": {
          "200": {
            "description": "Every matching contact.",
            "content": {
              "text/csv": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/kards/{share_link}/contacts/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ShareLink"
        },
        {
          "$ref": "#/components/parameters/Id"
        },
        {
          "$ref": "#/components/parameters/Organization"
        }
      ],
      "get": {
        "operationId": "getContact",
        "tags": [
          "Contacts"
        ],
        "summary": "One contact",
        "responses": {
          "200": {
            "description": "The contact.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContactEnvelope"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "operationId": "updateContact",
        "tags": [
          "Contacts"
        ],
        "summary": "Update a contact",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "contact"
                ],
                "properties": {
                  "contact": {
                    "$ref": "#/components/schemas/ContactInput"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated contact.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContactEnvelope"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          }
        }
      },
      "delete": {
        "operationId": "deleteContact",
        "tags": [
          "Contacts"
        ],
        "summary": "Delete a contact",
        "responses": {
          "200": {
            "$ref": "#/components/responses/Deleted"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/kards/{share_link}/events": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ShareLink"
        },
        {
          "$ref": "#/components/parameters/Organization"
        }
      ],
      "get": {
        "operationId": "listEvents",
        "tags": [
          "Events"
        ],
        "summary": "A kard's events",
        "responses": {
          "200": {
            "description": "The events.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "events"
                  ],
                  "properties": {
                    "events": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Event"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "post": {
        "operationId": "createEvent",
        "tags": [
          "Events"
        ],
        "summary": "Create an event",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "event"
                ],
                "properties": {
                  "event": {
                    "allOf": [
                      {
                        "$ref": "#/components/schemas/EventInput"
                      },
                      {
                        "required": [
                          "name"
                        ]
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EventEnvelope"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          }
        }
      }
    },
    "/kards/{share_link}/events/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ShareLink"
        },
        {
          "$ref": "#/components/parameters/Id"
        },
        {
          "$ref": "#/components/parameters/Organization"
        }
      ],
      "get": {
        "operationId": "getEvent",
        "tags": [
          "Events"
        ],
        "summary": "One event",
        "responses": {
          "200": {
            "description": "The event.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EventEnvelope"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "operationId": "updateEvent",
        "tags": [
          "Events"
        ],
        "summary": "Update an event",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "event"
                ],
                "properties": {
                  "event": {
                    "$ref": "#/components/schemas/EventInput"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated event.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EventEnvelope"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          }
        }
      },
      "delete": {
        "operationId": "deleteEvent",
        "tags": [
          "Events"
        ],
        "summary": "Delete an event (its contacts stay, detached)",
        "responses": {
          "200": {
            "$ref": "#/components/responses/Deleted"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/kards/{share_link}/documents": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ShareLink"
        },
        {
          "$ref": "#/components/parameters/Organization"
        }
      ],
      "get": {
        "operationId": "listDocuments",
        "tags": [
          "Documents"
        ],
        "summary": "A kard's documents",
        "responses": {
          "200": {
            "description": "The documents.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "documents"
                  ],
                  "properties": {
                    "documents": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Document"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "post": {
        "operationId": "uploadDocument",
        "tags": [
          "Documents"
        ],
        "summary": "Upload a document",
        "description": "PDF, Word or images, up to 10 MB each and 10 per kard. The slug is made from the file name when omitted.",
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file"
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "contentMediaType": "application/octet-stream"
                  },
                  "title": {
                    "type": "string"
                  },
                  "slug": {
                    "type": "string",
                    "pattern": "^[a-z0-9-]+$"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Uploaded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DocumentEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          }
        }
      }
    },
    "/kards/{share_link}/documents/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ShareLink"
        },
        {
          "$ref": "#/components/parameters/Id"
        },
        {
          "$ref": "#/components/parameters/Organization"
        }
      ],
      "get": {
        "operationId": "getDocument",
        "tags": [
          "Documents"
        ],
        "summary": "One document",
        "responses": {
          "200": {
            "description": "The document.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DocumentEnvelope"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "operationId": "updateDocument",
        "tags": [
          "Documents"
        ],
        "summary": "Rename a document or change its slug",
        "description": "Changing the slug changes its public URL and QR code.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "title": {
                    "type": "string"
                  },
                  "slug": {
                    "type": "string",
                    "pattern": "^[a-z0-9-]+$"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated document.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DocumentEnvelope"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          }
        }
      },
      "delete": {
        "operationId": "deleteDocument",
        "tags": [
          "Documents"
        ],
        "summary": "Delete a document",
        "responses": {
          "200": {
            "$ref": "#/components/responses/Deleted"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/kards/{share_link}/documents/{id}/replace": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ShareLink"
        },
        {
          "$ref": "#/components/parameters/Id"
        },
        {
          "$ref": "#/components/parameters/Organization"
        }
      ],
      "patch": {
        "operationId": "replaceDocumentFile",
        "tags": [
          "Documents"
        ],
        "summary": "Replace a document's file, keeping its slug, URL and QR code",
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file"
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "contentMediaType": "application/octet-stream"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The document with its new file.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DocumentEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          }
        }
      }
    },
    "/kards/{share_link}/documents/{document_id}/design": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ShareLink"
        },
        {
          "$ref": "#/components/parameters/DocumentId"
        },
        {
          "$ref": "#/components/parameters/Organization"
        }
      ],
      "get": {
        "operationId": "getDocumentDesign",
        "tags": [
          "Documents"
        ],
        "summary": "A document's landing page",
        "description": "Instead of opening the raw file, a document's URL can serve a custom HTML page (draft, then publish; sanitized on publish).",
        "responses": {
          "200": {
            "description": "The landing page.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DesignEnvelope"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "operationId": "saveDocumentDesign",
        "tags": [
          "Documents"
        ],
        "summary": "Save the landing page's draft (and publish it with `publish`)",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DesignInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The saved draft, or the published page with what the sanitizer removed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DesignEnvelope"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          }
        }
      }
    },
    "/kards/{share_link}/documents/{document_id}/design/publish": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ShareLink"
        },
        {
          "$ref": "#/components/parameters/DocumentId"
        },
        {
          "$ref": "#/components/parameters/Organization"
        }
      ],
      "post": {
        "operationId": "publishDocumentDesign",
        "tags": [
          "Documents"
        ],
        "summary": "Publish the landing page's draft",
        "description": "The response's `sanitizer_removals` lists anything stripped for safety; mention them to the owner.",
        "responses": {
          "200": {
            "description": "Published.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DesignEnvelope"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          }
        }
      }
    },
    "/kards/{share_link}/documents/{document_id}/design/unpublish": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ShareLink"
        },
        {
          "$ref": "#/components/parameters/DocumentId"
        },
        {
          "$ref": "#/components/parameters/Organization"
        }
      ],
      "delete": {
        "operationId": "unpublishDocumentDesign",
        "tags": [
          "Documents"
        ],
        "summary": "Take the landing page down (the QR opens the file again; the draft stays)",
        "responses": {
          "200": {
            "description": "Unpublished.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DesignEnvelope"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/kards/{share_link}/qr": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ShareLink"
        },
        {
          "$ref": "#/components/parameters/Organization"
        }
      ],
      "get": {
        "operationId": "getQr",
        "tags": [
          "QR code"
        ],
        "summary": "The kard's QR design and image URLs",
        "description": "The stored PNG is also public at `{kard url}/qr`.",
        "responses": {
          "200": {
            "description": "The QR code.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/QrEnvelope"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "operationId": "updateQr",
        "tags": [
          "QR code"
        ],
        "summary": "Restyle the QR code, set or remove its logo, or reset it",
        "description": "Three modes: design keys under `qr` (merged into the current design; out-of-range numbers are clamped), a `logo` file (multipart) or `remove_logo=1`, or `reset_default=1` for the stock design. The stored PNG is re-rendered.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "qr": {
                    "$ref": "#/components/schemas/QrDesign"
                  },
                  "remove_logo": {
                    "type": "string",
                    "enum": [
                      "1"
                    ]
                  },
                  "reset_default": {
                    "type": "string",
                    "enum": [
                      "1"
                    ]
                  }
                }
              }
            },
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "logo"
                ],
                "properties": {
                  "logo": {
                    "type": "string",
                    "contentMediaType": "image/png",
                    "description": "PNG, JPG, WebP or GIF, up to 2 MB."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The QR code as now drawn.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/QrEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          }
        }
      }
    },
    "/kards/{share_link}/qr/regenerate": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ShareLink"
        },
        {
          "$ref": "#/components/parameters/Organization"
        }
      ],
      "post": {
        "operationId": "regenerateQr",
        "tags": [
          "QR code"
        ],
        "summary": "Re-render the stored QR image",
        "responses": {
          "200": {
            "description": "The QR code.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/QrEnvelope"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/kards/{share_link}/design": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ShareLink"
        },
        {
          "$ref": "#/components/parameters/Organization"
        }
      ],
      "get": {
        "operationId": "getDesign",
        "tags": [
          "Custom design"
        ],
        "summary": "The kard's custom HTML page",
        "description": "Visitors only ever see published HTML; the standard layout shows until then.",
        "responses": {
          "200": {
            "description": "The design and how many versions it has.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/DesignEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "versions_count"
                      ],
                      "properties": {
                        "versions_count": {
                          "type": "integer"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "operationId": "saveDesign",
        "tags": [
          "Custom design"
        ],
        "summary": "Save the draft (and publish it with `publish`)",
        "description": "Up to 150,000 characters. Every save is kept in the history.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DesignInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The saved draft, or the published page with what the sanitizer removed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DesignEnvelope"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          }
        }
      }
    },
    "/kards/{share_link}/design/publish": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ShareLink"
        },
        {
          "$ref": "#/components/parameters/Organization"
        }
      ],
      "post": {
        "operationId": "publishDesign",
        "tags": [
          "Custom design"
        ],
        "summary": "Publish the draft as the kard's page",
        "description": "Sanitized on publish; `sanitizer_removals` lists anything stripped (scripts, external iframes...). Mention removals to the owner.",
        "responses": {
          "200": {
            "description": "Published.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DesignEnvelope"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          }
        }
      }
    },
    "/kards/{share_link}/design/unpublish": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ShareLink"
        },
        {
          "$ref": "#/components/parameters/Organization"
        }
      ],
      "delete": {
        "operationId": "unpublishDesign",
        "tags": [
          "Custom design"
        ],
        "summary": "Return to the standard layout (the draft stays)",
        "responses": {
          "200": {
            "description": "Unpublished.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DesignEnvelope"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/kards/{share_link}/design/versions": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ShareLink"
        },
        {
          "$ref": "#/components/parameters/Organization"
        }
      ],
      "get": {
        "operationId": "listDesignVersions",
        "tags": [
          "Custom design"
        ],
        "summary": "The last 50 versions of the design",
        "responses": {
          "200": {
            "description": "Newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "versions"
                  ],
                  "properties": {
                    "versions": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/DesignVersion"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/kards/{share_link}/design/restore": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ShareLink"
        },
        {
          "$ref": "#/components/parameters/Organization"
        }
      ],
      "post": {
        "operationId": "restoreDesignVersion",
        "tags": [
          "Custom design"
        ],
        "summary": "Make an earlier version the draft (nothing goes live until publish)",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "version_id"
                ],
                "properties": {
                  "version_id": {
                    "type": "integer"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The restored draft.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/DesignEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "restored_version_id"
                      ],
                      "properties": {
                        "restored_version_id": {
                          "type": "integer"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/kards/{share_link}/design/checkpoint": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ShareLink"
        },
        {
          "$ref": "#/components/parameters/Organization"
        }
      ],
      "post": {
        "operationId": "createDesignCheckpoint",
        "tags": [
          "Custom design"
        ],
        "summary": "Name the current draft as a safety point (up to 25)",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "label": {
                    "type": "string",
                    "maxLength": 120
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "The checkpoint.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "checkpoint"
                  ],
                  "properties": {
                    "checkpoint": {
                      "$ref": "#/components/schemas/DesignVersion"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          }
        }
      }
    },
    "/kards/{share_link}/design/templates": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ShareLink"
        },
        {
          "$ref": "#/components/parameters/Organization"
        }
      ],
      "get": {
        "operationId": "listDesignTemplates",
        "tags": [
          "Custom design"
        ],
        "summary": "The built-in templates",
        "responses": {
          "200": {
            "description": "The templates.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "templates"
                  ],
                  "properties": {
                    "templates": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": [
                          "slug",
                          "name",
                          "blurb"
                        ],
                        "properties": {
                          "slug": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string"
                          },
                          "blurb": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/kards/{share_link}/design/apply_template": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ShareLink"
        },
        {
          "$ref": "#/components/parameters/Organization"
        }
      ],
      "post": {
        "operationId": "applyDesignTemplate",
        "tags": [
          "Custom design"
        ],
        "summary": "Make a template the draft (the previous draft stays in the history)",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "template"
                ],
                "properties": {
                  "template": {
                    "type": "string",
                    "description": "A slug from the templates list."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The new draft.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/DesignEnvelope"
                    },
                    {
                      "type": "object",
                      "required": [
                        "applied_template"
                      ],
                      "properties": {
                        "applied_template": {
                          "type": "string"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/organizations": {
      "get": {
        "operationId": "listOrganizations",
        "tags": [
          "Organizations"
        ],
        "summary": "The account's organizations, with its role in each",
        "responses": {
          "200": {
            "description": "The organizations.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "organizations"
                  ],
                  "properties": {
                    "organizations": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Organization"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/organizations/{slug}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/OrganizationSlug"
        }
      ],
      "get": {
        "operationId": "getOrganization",
        "tags": [
          "Organizations"
        ],
        "summary": "One organization (admins only)",
        "responses": {
          "200": {
            "description": "The organization.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OrganizationEnvelope"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "operationId": "updateOrganization",
        "tags": [
          "Organizations"
        ],
        "summary": "Configure an organization (admins only)",
        "description": "Creating or deleting organizations isn't self-serve. Send `logo` as multipart, or `remove_logo=1`.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "organization"
                ],
                "properties": {
                  "organization": {
                    "$ref": "#/components/schemas/OrganizationInput"
                  }
                }
              }
            },
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "organization[logo]": {
                    "type": "string",
                    "contentMediaType": "image/png"
                  },
                  "organization[remove_logo]": {
                    "type": "string",
                    "enum": [
                      "1"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated organization.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OrganizationEnvelope"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          }
        }
      }
    },
    "/organizations/{slug}/kards": {
      "parameters": [
        {
          "$ref": "#/components/parameters/OrganizationSlug"
        }
      ],
      "get": {
        "operationId": "listOrganizationKards",
        "tags": [
          "Organizations"
        ],
        "summary": "Every kard in an organization (admins only)",
        "responses": {
          "200": {
            "description": "The kards.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "kards"
                  ],
                  "properties": {
                    "kards": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/KardSummary"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/bookmarks": {
      "get": {
        "operationId": "listBookmarks",
        "tags": [
          "Bookmarks"
        ],
        "summary": "Kards this account saved, newest first",
        "responses": {
          "200": {
            "description": "The bookmarks.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "bookmarks"
                  ],
                  "properties": {
                    "bookmarks": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Bookmark"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "post": {
        "operationId": "createBookmark",
        "tags": [
          "Bookmarks"
        ],
        "summary": "Bookmark any public kard",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "share_link"
                ],
                "properties": {
                  "share_link": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Bookmarked (or already was).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "bookmark"
                  ],
                  "properties": {
                    "bookmark": {
                      "$ref": "#/components/schemas/Bookmark"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/bookmarks/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "delete": {
        "operationId": "deleteBookmark",
        "tags": [
          "Bookmarks"
        ],
        "summary": "Remove a bookmark",
        "responses": {
          "200": {
            "$ref": "#/components/responses/Deleted"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/bookings": {
      "get": {
        "operationId": "listAccountBookings",
        "tags": [
          "Bookings"
        ],
        "summary": "Bookings across every calendar the account manages, earliest first",
        "description": "The account's own calendar (behind its personal kards) and those of organizations it administers. Each booking carries its `calendar`.",
        "parameters": [
          {
            "$ref": "#/components/parameters/From"
          },
          {
            "$ref": "#/components/parameters/To"
          },
          {
            "$ref": "#/components/parameters/BookingStatuses"
          },
          {
            "$ref": "#/components/parameters/Page"
          },
          {
            "$ref": "#/components/parameters/PerPage"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of bookings.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BookingPage"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/kards/{share_link}/offerings": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ShareLink"
        },
        {
          "$ref": "#/components/parameters/Organization"
        }
      ],
      "get": {
        "operationId": "listOfferings",
        "tags": [
          "Bookings"
        ],
        "summary": "What can be booked from a kard",
        "description": "A team kard linked to a person lists what that person does. The `kard` block says whether the kard shows a Book button.",
        "responses": {
          "200": {
            "description": "The kard's Book button, its calendar and the offerings.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "kard",
                    "calendar",
                    "offerings"
                  ],
                  "properties": {
                    "kard": {
                      "$ref": "#/components/schemas/KardBooking"
                    },
                    "calendar": {
                      "$ref": "#/components/schemas/SchedulingCalendar"
                    },
                    "offerings": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Offering"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NoCalendar"
          }
        }
      }
    },
    "/kards/{share_link}/availability": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ShareLink"
        },
        {
          "$ref": "#/components/parameters/Organization"
        }
      ],
      "get": {
        "operationId": "getAvailability",
        "tags": [
          "Bookings"
        ],
        "summary": "Open times for a visit, by local date",
        "description": "One or more offerings, back to back with one person. `provider_ids` on each time lists who is free, in assignment order.",
        "parameters": [
          {
            "name": "offering_ids[]",
            "in": "query",
            "required": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "integer"
              }
            },
            "style": "form",
            "explode": true
          },
          {
            "name": "date",
            "in": "query",
            "description": "The first local date (YYYY-MM-DD); today in the calendar's zone when omitted.",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "days",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 14,
              "default": 1
            }
          },
          {
            "name": "provider_id",
            "in": "query",
            "description": "Who with. A team kard linked to a person defaults to them.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "duration",
            "in": "query",
            "description": "One of the offering's duration options, in minutes.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "mode",
            "in": "query",
            "description": "`online`: what a visitor can book. `front_desk`: what the business can, ignoring notice and the booking window.",
            "schema": {
              "type": "string",
              "enum": [
                "online",
                "front_desk"
              ],
              "default": "online"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Open times.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "calendar",
                    "offerings",
                    "provider",
                    "mode",
                    "dates"
                  ],
                  "properties": {
                    "calendar": {
                      "$ref": "#/components/schemas/SchedulingCalendar"
                    },
                    "offerings": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/NamedRecord"
                      }
                    },
                    "provider": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/NamedRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "mode": {
                      "type": "string",
                      "enum": [
                        "online",
                        "front_desk"
                      ]
                    },
                    "dates": {
                      "type": "object",
                      "description": "Local dates (YYYY-MM-DD) to their open times.",
                      "additionalProperties": {
                        "type": "array",
                        "items": {
                          "$ref": "#/components/schemas/Slot"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NoCalendar"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          }
        }
      }
    },
    "/kards/{share_link}/bookings": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ShareLink"
        },
        {
          "$ref": "#/components/parameters/Organization"
        }
      ],
      "get": {
        "operationId": "listKardBookings",
        "tags": [
          "Bookings"
        ],
        "summary": "Bookings of the calendar behind a kard",
        "parameters": [
          {
            "$ref": "#/components/parameters/From"
          },
          {
            "$ref": "#/components/parameters/To"
          },
          {
            "$ref": "#/components/parameters/BookingStatuses"
          },
          {
            "$ref": "#/components/parameters/Page"
          },
          {
            "$ref": "#/components/parameters/PerPage"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of bookings.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BookingPage"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NoCalendar"
          }
        }
      },
      "post": {
        "operationId": "createBooking",
        "tags": [
          "Bookings"
        ],
        "summary": "Book like the front desk",
        "description": "`source: \"api\"`: no notice or booking window, any active person. Send an `idempotency_key`: a retry with the same key returns the first booking instead of booking twice (keys are per API user).\n\nEvery booking emails its client and guests, so `client.email` and each guest must be one mailbox on a public domain, and a booking made here names at most 2 guests (else `422 invalid_recipient`, and nothing is booked or sent). Limits, counted together with the web's front desk: per account 5 new bookings a minute and 200 a day; per calendar 10 a minute and 300 a day (`429 rate_limited` with `Retry-After`).\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "booking"
                ],
                "properties": {
                  "booking": {
                    "$ref": "#/components/schemas/BookingInput"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Booked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BookingEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NoCalendar"
          },
          "409": {
            "$ref": "#/components/responses/SlotTaken"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/kards/{share_link}/bookings/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ShareLink"
        },
        {
          "$ref": "#/components/parameters/Id"
        },
        {
          "$ref": "#/components/parameters/Organization"
        }
      ],
      "get": {
        "operationId": "getBooking",
        "tags": [
          "Bookings"
        ],
        "summary": "One booking",
        "responses": {
          "200": {
            "description": "The booking.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BookingEnvelope"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "operationId": "updateBooking",
        "tags": [
          "Bookings"
        ],
        "summary": "Move a booking, or change its status",
        "description": "Either a new time (`starts_at`, optionally `provider_id` and `overlap_allowed`; the client's manage link and reminders follow) or a new `status`, not both. Statuses: `arrived`, `completed`, `no_show` (after the start), `booked` (approve a request), `declined` and `cancelled` (both take an optional `reason`; cancelling here is the business's).\n\nLimits, counted together with the web's front desk: 30 changes a minute per account, and 5 an hour per booking (`429 rate_limited` with `Retry-After`).\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "booking"
                ],
                "properties": {
                  "booking": {
                    "$ref": "#/components/schemas/BookingUpdate"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The booking as it is now.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BookingEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/SlotTaken"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/calendars": {
      "get": {
        "operationId": "listCalendars",
        "tags": [
          "Calendar"
        ],
        "summary": "The calendars this account works in, with its role on each",
        "description": "Any key. Its personal calendar, its organizations' calendars (as an admin), and those it signs in to as front desk or staff. `role` is `admin`, `front_desk` or `staff`, and `role_summary` says what that role can do. Everything inside one is under `/calendars/{calendar_id}/…`. An account that works in no calendar gets an empty list.\n",
        "responses": {
          "200": {
            "description": "The calendars.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "calendars"
                  ],
                  "properties": {
                    "calendars": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CalendarSummary"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      },
      "post": {
        "operationId": "createCalendar",
        "tags": [
          "Calendar setup"
        ],
        "summary": "Set a calendar up, with the setup wizard's choices",
        "description": "Any key, for its own calendar; an organization's (`organization`: its slug) for that organization's admins only. A member who isn't an admin gets `403`, naming the admins; anyone else gets `404`. The setup wizard's steps in one call, in its order and all or nothing: the kind of business (`preset`, with `time_zone`, `currency` and `country`), the team (organizations: team Kards, people by name, or yourself), `opening_hours`, and the menu (`offerings`: `\"starters\"`, the kind's starter menu, or your own rows; a row can start from a starter, `{\"starter\": 1, \"price_cents\": 120000}`). With a menu, online booking turns on unless `accepting_bookings` is false. Read the kinds and their starter menus from `GET /calendars/presets`.\n\nA step that can't be saved is `422 validation_failed`, with `details` keyed by step (`kind`, `team`, `opening_hours`, `offerings`), and nothing is set up. A calendar that already exists is `409 calendar_exists` (`details.calendar_id`); change it with `PATCH /calendars/{calendar_id}/settings`. Limit: 30 changes a minute per account.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "calendar"
                ],
                "properties": {
                  "calendar": {
                    "$ref": "#/components/schemas/CalendarSetupInput"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Set up. `setup` says which wizard steps there are, the last one done and what's next; `message` is the line to relay.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "calendar",
                    "setup",
                    "message"
                  ],
                  "properties": {
                    "calendar": {
                      "$ref": "#/components/schemas/Calendar"
                    },
                    "setup": {
                      "type": "object",
                      "required": [
                        "steps",
                        "finished",
                        "next"
                      ],
                      "properties": {
                        "steps": {
                          "type": "array",
                          "items": {
                            "type": "string",
                            "enum": [
                              "kind",
                              "team",
                              "hours",
                              "offerings",
                              "share"
                            ]
                          }
                        },
                        "finished": {
                          "type": "string",
                          "enum": [
                            "kind",
                            "team",
                            "hours",
                            "offerings"
                          ]
                        },
                        "next": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "enum": [
                            "kind",
                            "team",
                            "hours",
                            "offerings",
                            "share",
                            null
                          ]
                        }
                      }
                    },
                    "message": {
                      "type": "string",
                      "examples": [
                        "Bloom Salon is set up. Online booking is live. “Book appointment” shows on 2 Kards."
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "This account or organization already has a calendar (`calendar_exists`; `details.calendar_id`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/calendars/presets": {
      "get": {
        "operationId": "listCalendarPresets",
        "tags": [
          "Calendar setup"
        ],
        "summary": "The kinds of business a calendar can be, with their starter menus",
        "description": "Any key. Setup's first step, \"What kind of business?\": each kind's words (`labels`), what it switches on, its default booking rules and opening hours, and its starter menu priced in `currency` (the account's country's currency when omitted). A starter with `needs_price: true` has no price in that currency yet: ask the owner for one.\n",
        "parameters": [
          {
            "name": "currency",
            "in": "query",
            "description": "ISO 4217, such as INR, USD or GBP.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Every kind of business.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "presets"
                  ],
                  "properties": {
                    "presets": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CalendarPresetDetail"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/calendars/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "The calendar's id, from GET /calendars.",
          "schema": {
            "type": "integer"
          }
        }
      ],
      "get": {
        "operationId": "getCalendar",
        "tags": [
          "Calendar"
        ],
        "summary": "One calendar, with its team, features and menu",
        "description": "Every role. `your_provider` is the person this login is on the team, if any. `offerings`, the menu the desk books from (front-desk-only ones included), is for the front desk and admins; it is null for staff. A calendar the key has no role on is a 404, like one that doesn't exist.\n",
        "responses": {
          "200": {
            "description": "The calendar.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "calendar"
                  ],
                  "properties": {
                    "calendar": {
                      "$ref": "#/components/schemas/Calendar"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          }
        }
      }
    },
    "/calendars/{calendar_id}/schedule": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CalendarId"
        }
      ],
      "get": {
        "operationId": "getCalendarSchedule",
        "tags": [
          "Calendar"
        ],
        "summary": "The desk's day or week",
        "description": "Every role: \"what does my Tuesday look like?\". The day (the default) has each person's hours, leave, breaks and blocked time and their visits, who is free now, and the day's summary. `view=week` is Monday to Sunday around `date`: each day's status, blocks and every visit.\n\nA visit here is who, what, when and its state, with the client's alert note where this login sees the client's details (staff: their own visits). The client's other details are on `GET /calendars/{calendar_id}/bookings/{id}`.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/CalendarDate"
          },
          {
            "name": "view",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "day",
                "week"
              ],
              "default": "day"
            }
          },
          {
            "name": "provider_id",
            "in": "query",
            "description": "One person only.",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The day, or the week.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "calendar",
                    "schedule"
                  ],
                  "properties": {
                    "calendar": {
                      "$ref": "#/components/schemas/SchedulingCalendar"
                    },
                    "schedule": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/ScheduleDay"
                        },
                        {
                          "$ref": "#/components/schemas/ScheduleWeek"
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          }
        }
      }
    },
    "/calendars/{calendar_id}/availability": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CalendarId"
        }
      ],
      "get": {
        "operationId": "getCalendarAvailability",
        "tags": [
          "Calendar"
        ],
        "summary": "Free times for a visit, or for moving a booking",
        "description": "The front desk and admins (the web's new-booking and reschedule sheets). Send `offering_ids[]` (one or more offerings, back to back with one person: anyone who does them, unless `provider_id` names one), or `booking_id` for where that booking could move (its services and timing, its own time not counted, its person unless `provider_id` names another). Times come grouped by local date; `provider_ids` on each lists who is free, best first.\n\n`mode=front_desk` (the default here) is what the business may book, ignoring notice and the booking window; `mode=online` is what clients can book.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/OfferingIds"
          },
          {
            "name": "booking_id",
            "in": "query",
            "description": "A booking of this calendar, instead of `offering_ids[]`.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/CalendarDate"
          },
          {
            "name": "days",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 14,
              "default": 1
            }
          },
          {
            "name": "provider_id",
            "in": "query",
            "description": "One person.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "duration",
            "in": "query",
            "description": "One of the first offering's duration options, in minutes.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "client_id",
            "in": "query",
            "description": "Their usual person first, when anyone will do.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/AvailabilityMode"
          }
        ],
        "responses": {
          "200": {
            "description": "Free times.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "calendar",
                    "offerings",
                    "provider",
                    "booking",
                    "mode",
                    "dates"
                  ],
                  "properties": {
                    "calendar": {
                      "$ref": "#/components/schemas/SchedulingCalendar"
                    },
                    "offerings": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/NamedRecord"
                      }
                    },
                    "provider": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/NamedRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "booking": {
                      "description": "The booking being moved, with `booking_id`.",
                      "oneOf": [
                        {
                          "type": "object",
                          "required": [
                            "id"
                          ],
                          "properties": {
                            "id": {
                              "type": "integer"
                            }
                          }
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "mode": {
                      "type": "string",
                      "enum": [
                        "front_desk",
                        "online"
                      ]
                    },
                    "dates": {
                      "type": "object",
                      "description": "Local dates (YYYY-MM-DD) to their free times.",
                      "additionalProperties": {
                        "type": "array",
                        "items": {
                          "$ref": "#/components/schemas/Slot"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          }
        }
      }
    },
    "/calendars/{calendar_id}/availability/explain": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CalendarId"
        }
      ],
      "get": {
        "operationId": "explainCalendarAvailability",
        "tags": [
          "Calendar"
        ],
        "summary": "Why a time isn't free, person by person",
        "description": "The front desk and admins. For the person `provider_id` names, or else each active person who does every offering: `free`, and the `reasons` in words (\"booked: Meera Iyer, Haircut\"), empty when the time is free for them. With `mode=online`, also why clients can't book it online (\"within the 2-hour minimum notice\").\n",
        "parameters": [
          {
            "name": "starts_at",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "$ref": "#/components/parameters/OfferingIds"
          },
          {
            "name": "booking_id",
            "in": "query",
            "description": "A booking of this calendar, instead of `offering_ids[]`; its own time doesn't count.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "provider_id",
            "in": "query",
            "description": "One person.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "duration",
            "in": "query",
            "description": "One of the first offering's duration options, in minutes.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/AvailabilityMode"
          }
        ],
        "responses": {
          "200": {
            "description": "Each person, and why not.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "starts_at",
                    "local_starts_at",
                    "mode",
                    "people"
                  ],
                  "properties": {
                    "starts_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "local_starts_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "mode": {
                      "type": "string",
                      "enum": [
                        "front_desk",
                        "online"
                      ]
                    },
                    "people": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": [
                          "provider",
                          "free",
                          "reasons"
                        ],
                        "properties": {
                          "provider": {
                            "$ref": "#/components/schemas/NamedRecord"
                          },
                          "free": {
                            "type": "boolean"
                          },
                          "reasons": {
                            "type": "array",
                            "items": {
                              "type": "string"
                            },
                            "examples": [
                              [
                                "booked: Meera Iyer, Haircut"
                              ],
                              [
                                "within the 2-hour minimum notice"
                              ]
                            ]
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          }
        }
      }
    },
    "/calendars/{calendar_id}/bookings": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CalendarId"
        }
      ],
      "get": {
        "operationId": "listCalendarBookings",
        "tags": [
          "Calendar"
        ],
        "summary": "The calendar's bookings, earliest first",
        "description": "Every role: every login reads every visit, as on the web. Staff see the client's details (phone, email, alert note, notes, answers, guests, address) and the team note only on their own visits, null on the others', and `payment` and `advance` are null for them; `can` says what this login may do with each booking. A request whose hold ran out is released time and isn't listed. `status=requested` is the list of requests to approve.\n",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "description": "A comma-separated list of requested, booked, arrived, completed, cancelled, no_show, declined; or `all`.",
            "schema": {
              "type": "string",
              "default": "requested,booked"
            }
          },
          {
            "name": "date",
            "in": "query",
            "description": "A local date (YYYY-MM-DD) in the calendar's zone, for that day's bookings; instead of `from` and `to`.",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "from",
            "in": "query",
            "description": "ISO 8601; now when omitted, unless `to` or `date` is sent.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "$ref": "#/components/parameters/To"
          },
          {
            "name": "provider_id",
            "in": "query",
            "description": "Bookings with this person.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "client_id",
            "in": "query",
            "description": "This client's bookings.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "source",
            "in": "query",
            "description": "A comma-separated list of online, front_desk, walk_in, api.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "paid",
            "in": "query",
            "description": "Only visits marked paid (`true`), or only those not (`false`).",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "order",
            "in": "query",
            "description": "`desc` for the latest first.",
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "asc"
            }
          },
          {
            "$ref": "#/components/parameters/Page"
          },
          {
            "$ref": "#/components/parameters/PerPage"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of bookings.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeskBookingPage"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          }
        }
      },
      "post": {
        "operationId": "createCalendarBooking",
        "tags": [
          "Calendar"
        ],
        "summary": "Book like the front desk, or add a walk-in",
        "description": "The front desk and admins. Like the desk's new-booking sheet: no notice or booking window, any active person (or the one `provider_id` names); `overlap_allowed` with a `provider_id` books anyway over that person's time. Send an `idempotency_key`: a retry with the same key returns the first booking instead of booking twice (keys are per API user).\n\n`source: \"walk_in\"` (only where the calendar's `features.walk_ins` is on) is someone in the shop now: it starts now unless `starts_at` says otherwise, is marked arrived, and without a client files under the desk's anonymous \"Walk-in\".\n\nEvery booking emails its client and guests, so `client.email` and each guest must be one mailbox on a public domain, and a booking made here names at most 2 guests (else `422 invalid_recipient`, and nothing is booked or sent). Limits, shared with `POST /kards/{share_link}/bookings` and the web's front desk: per account 5 new bookings a minute and 200 a day; per calendar 10 a minute and 300 a day (`429 rate_limited` with `Retry-After`).\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "booking"
                ],
                "properties": {
                  "booking": {
                    "$ref": "#/components/schemas/CalendarBookingInput"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Booked.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeskBookingEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          },
          "409": {
            "$ref": "#/components/responses/SlotTaken"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/calendars/{calendar_id}/bookings/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CalendarId"
        },
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "get": {
        "operationId": "getCalendarBooking",
        "tags": [
          "Calendar"
        ],
        "summary": "One booking, as the desk's booking sheet shows it",
        "description": "Every role, with the client's last 3 earlier visits (`history`). Staff: the client's details, the team note and `history` only on their own visits (null on the others'), and no money.\n",
        "responses": {
          "200": {
            "description": "The booking.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeskBookingEnvelope"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          }
        }
      },
      "patch": {
        "operationId": "updateCalendarBookingNote",
        "tags": [
          "Calendar"
        ],
        "summary": "Change the team's note on a booking",
        "description": "Admins, the front desk, and staff on their own visits. Only `internal_notes` (the team's note; clients never see it; `\"\"` clears it): statuses, times and money have their own operations. Limit: 30 changes a minute per account (`429 rate_limited`).\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "internal_notes"
                ],
                "properties": {
                  "internal_notes": {
                    "type": "string",
                    "examples": [
                      "Bring the round brush"
                    ]
                  },
                  "lock_version": {
                    "$ref": "#/components/schemas/LockVersion"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The booking as it is now.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeskBookingEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          },
          "409": {
            "$ref": "#/components/responses/StaleBooking"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/calendars/{calendar_id}/bookings/{id}/arrive": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CalendarId"
        },
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "markCalendarBookingArrived",
        "tags": [
          "Calendar"
        ],
        "summary": "Mark a visit arrived",
        "description": "Admins, the front desk, and staff on their own visits. Limit: 30 changes a minute per account.\n",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "lock_version": {
                    "$ref": "#/components/schemas/LockVersion"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/CalendarBooking"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          },
          "409": {
            "$ref": "#/components/responses/StaleBooking"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/calendars/{calendar_id}/bookings/{id}/complete": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CalendarId"
        },
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "markCalendarBookingCompleted",
        "tags": [
          "Calendar"
        ],
        "summary": "Mark a visit completed",
        "description": "Admins, the front desk, and staff on their own visits. Limit: 30 changes a minute per account.\n",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "lock_version": {
                    "$ref": "#/components/schemas/LockVersion"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/CalendarBooking"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          },
          "409": {
            "$ref": "#/components/responses/StaleBooking"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/calendars/{calendar_id}/bookings/{id}/no_show": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CalendarId"
        },
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "markCalendarBookingNoShow",
        "tags": [
          "Calendar"
        ],
        "summary": "Mark a no-show",
        "description": "Admins, the front desk, and staff on their own visits. Only once the visit has started. Limit: 30 changes a minute per account.\n",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "lock_version": {
                    "$ref": "#/components/schemas/LockVersion"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/CalendarBooking"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          },
          "409": {
            "$ref": "#/components/responses/StaleBooking"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/calendars/{calendar_id}/bookings/{id}/undo_status": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CalendarId"
        },
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "undoCalendarBookingStatus",
        "tags": [
          "Calendar"
        ],
        "summary": "Undo arrived, completed or no-show",
        "description": "Admins, the front desk, and staff on their own visits. The web's Undo: puts an arrived, completed or no-show visit back to the status it had (`to`: `booked` or `arrived`). Limit: 30 changes a minute per account.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "to"
                ],
                "properties": {
                  "to": {
                    "type": "string",
                    "enum": [
                      "booked",
                      "arrived"
                    ]
                  },
                  "lock_version": {
                    "$ref": "#/components/schemas/LockVersion"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/CalendarBooking"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          },
          "409": {
            "$ref": "#/components/responses/StaleBooking"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/calendars/{calendar_id}/bookings/{id}/approve": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CalendarId"
        },
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "approveCalendarBooking",
        "tags": [
          "Calendar"
        ],
        "summary": "Approve a request",
        "description": "The front desk and admins. A request (`status: requested`) becomes booked, and the client is told. Limits: 30 changes a minute per account, and 5 an hour per booking, since each one emails the client.\n",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "lock_version": {
                    "$ref": "#/components/schemas/LockVersion"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/CalendarBooking"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          },
          "409": {
            "$ref": "#/components/responses/StaleBooking"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/calendars/{calendar_id}/bookings/{id}/decline": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CalendarId"
        },
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "declineCalendarBooking",
        "tags": [
          "Calendar"
        ],
        "summary": "Decline a request",
        "description": "The front desk and admins. The client is told, with your `reason` when you give one. Limits: 30 changes a minute per account, and 5 an hour per booking, since each one emails the client.\n",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "reason": {
                    "type": "string",
                    "description": "Optional; the client reads it."
                  },
                  "lock_version": {
                    "$ref": "#/components/schemas/LockVersion"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/CalendarBooking"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          },
          "409": {
            "$ref": "#/components/responses/StaleBooking"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/calendars/{calendar_id}/bookings/{id}/cancel": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CalendarId"
        },
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "cancelCalendarBooking",
        "tags": [
          "Calendar"
        ],
        "summary": "Cancel a booking, as the business",
        "description": "The front desk and admins. The `reason` is required (else `422 validation_failed`, with `details.reason`): the client's message and the team both read it. Limits: 30 changes a minute per account, and 5 an hour per booking, since each one emails the client.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "reason"
                ],
                "properties": {
                  "reason": {
                    "type": "string",
                    "examples": [
                      "Stylist unwell"
                    ]
                  },
                  "lock_version": {
                    "$ref": "#/components/schemas/LockVersion"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/CalendarBooking"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          },
          "409": {
            "$ref": "#/components/responses/StaleBooking"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/calendars/{calendar_id}/bookings/{id}/reschedule": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CalendarId"
        },
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "rescheduleCalendarBooking",
        "tags": [
          "Calendar"
        ],
        "summary": "Move a booking to a new time or person",
        "description": "The front desk and admins. The client's manage link and reminders follow, and the client is told. `provider_id` moves it to another person (else it stays with its own); `overlap_allowed: true` moves it anyway over that person's time. Limits: 30 changes a minute per account, and 5 an hour per booking, since each one emails the client.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "starts_at"
                ],
                "properties": {
                  "starts_at": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "provider_id": {
                    "type": "integer"
                  },
                  "overlap_allowed": {
                    "type": "boolean",
                    "default": false
                  },
                  "lock_version": {
                    "$ref": "#/components/schemas/LockVersion"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/CalendarBooking"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          },
          "409": {
            "$ref": "#/components/responses/SlotTaken"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/calendars/{calendar_id}/bookings/{id}/resize": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CalendarId"
        },
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "resizeCalendarBooking",
        "tags": [
          "Calendar"
        ],
        "summary": "Make a visit longer or shorter",
        "description": "The front desk and admins. The day grid's resize: `duration_minutes` is the whole visit's length from its start. The team's business: the client isn't messaged. Limit: 30 changes a minute per account.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "duration_minutes"
                ],
                "properties": {
                  "duration_minutes": {
                    "type": "integer",
                    "minimum": 1,
                    "examples": [
                      75
                    ]
                  },
                  "overlap_allowed": {
                    "type": "boolean",
                    "default": false
                  },
                  "lock_version": {
                    "$ref": "#/components/schemas/LockVersion"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/CalendarBooking"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          },
          "409": {
            "$ref": "#/components/responses/SlotTaken"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/calendars/{calendar_id}/bookings/{id}/confirm": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CalendarId"
        },
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "confirmCalendarBooking",
        "tags": [
          "Calendar"
        ],
        "summary": "Mark a booking confirmed",
        "description": "The front desk and admins. \"Mark confirmed\": the client confirmed some other way (a call, a message). Only for a visit still to come that is booked or requested (else `422 invalid_transition`); one already confirmed keeps its first time. Limit: 30 changes a minute per account.\n",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "lock_version": {
                    "$ref": "#/components/schemas/LockVersion"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/CalendarBooking"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          },
          "409": {
            "$ref": "#/components/responses/StaleBooking"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/calendars/{calendar_id}/bookings/{booking_id}/payment": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CalendarId"
        },
        {
          "$ref": "#/components/parameters/BookingId"
        }
      ],
      "patch": {
        "operationId": "markCalendarBookingPaid",
        "tags": [
          "Calendar"
        ],
        "summary": "Mark a visit paid, or change what was recorded",
        "description": "The front desk and admins. Every field is optional, as on the web's booking sheet: the amount starts from what's due (the price less an advance received), the method from the last one used here, the time from now. Only a visit that has started can be marked paid. `PUT` is the same. Limit: 30 changes a minute per account.\n",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "amount_cents": {
                    "type": "integer",
                    "minimum": 0,
                    "description": "Minor units of the booking's currency."
                  },
                  "tip_cents": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "minimum": 0,
                    "description": "`null` clears it."
                  },
                  "payment_method": {
                    "type": "string",
                    "enum": [
                      "cash",
                      "upi",
                      "card",
                      "other"
                    ]
                  },
                  "paid_at": {
                    "type": "string",
                    "format": "date-time",
                    "description": "Not in the future."
                  },
                  "lock_version": {
                    "$ref": "#/components/schemas/LockVersion"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/CalendarBooking"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          },
          "409": {
            "$ref": "#/components/responses/StaleBooking"
          },
          "422": {
            "description": "Not saved (`validation_failed`); `details` has the sheet's message per field (`amount_cents`, `tip_cents`, `payment_method`, `paid_at`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "operationId": "clearCalendarBookingPayment",
        "tags": [
          "Calendar"
        ],
        "summary": "Mark a visit not paid after all",
        "description": "The front desk and admins. Forgets the amount, tip, method and time. Limit: 30 changes a minute per account.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/LockVersion"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/CalendarBooking"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          },
          "409": {
            "$ref": "#/components/responses/StaleBooking"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/calendars/{calendar_id}/bookings/{booking_id}/advance": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CalendarId"
        },
        {
          "$ref": "#/components/parameters/BookingId"
        }
      ],
      "patch": {
        "operationId": "markCalendarBookingAdvanceReceived",
        "tags": [
          "Calendar"
        ],
        "summary": "Mark the client's advance received",
        "description": "The front desk and admins. The client is emailed. `approve: true` approves a request in the same step. A booking with no advance, or one already marked received, is `422 invalid_transition`. `PUT` is the same. Limits: 30 changes a minute per account, and 5 an hour per booking, since each one emails the client.\n",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "approve": {
                    "type": "boolean",
                    "default": false
                  },
                  "lock_version": {
                    "$ref": "#/components/schemas/LockVersion"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/CalendarBooking"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          },
          "409": {
            "$ref": "#/components/responses/StaleBooking"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "operationId": "undoCalendarBookingAdvance",
        "tags": [
          "Calendar"
        ],
        "summary": "Mark the advance not received after all",
        "description": "The front desk and admins. An advance not marked received is `422 invalid_transition`. Limit: 30 changes a minute per account.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/LockVersion"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/CalendarBooking"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          },
          "409": {
            "$ref": "#/components/responses/StaleBooking"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/calendars/{calendar_id}/bookings/{booking_id}/advance-outcome": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CalendarId"
        },
        {
          "$ref": "#/components/parameters/BookingId"
        }
      ],
      "patch": {
        "operationId": "setCalendarBookingAdvanceOutcome",
        "tags": [
          "Calendar"
        ],
        "summary": "Say whether a cancelled visit's advance was refunded or kept",
        "description": "The front desk and admins. Cancelling refunds nothing by itself: once a visit with a received advance is cancelled or declined, record `outcome: \"refunded\"` or `\"kept\"`, and the client's booking page says so (refunded advances leave takings). Any other visit is `422 invalid_transition`; an `outcome` that isn't one of the two is `400 invalid_parameter`. `PUT` is the same. Limit: 30 changes a minute per account.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "outcome"
                ],
                "properties": {
                  "outcome": {
                    "type": "string",
                    "enum": [
                      "refunded",
                      "kept"
                    ]
                  },
                  "lock_version": {
                    "$ref": "#/components/schemas/LockVersion"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/CalendarBooking"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          },
          "409": {
            "$ref": "#/components/responses/StaleBooking"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "operationId": "clearCalendarBookingAdvanceOutcome",
        "tags": [
          "Calendar"
        ],
        "summary": "Forget whether the advance was refunded or kept",
        "description": "The front desk and admins. Back to not said: the client's page says to ask about it again. Limit: 30 changes a minute per account.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/LockVersion"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/CalendarBooking"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          },
          "409": {
            "$ref": "#/components/responses/StaleBooking"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/calendars/{calendar_id}/clients": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CalendarId"
        }
      ],
      "get": {
        "operationId": "listCalendarClients",
        "tags": [
          "Calendar"
        ],
        "summary": "The calendar's clients, or a search",
        "description": "The front desk and admins. `q` searches part of a name or email, or any 3+ digits of a phone number (best matches first); without it, `sort` orders the list. Each client comes with their counts, next booking and usual service. Notes and alert notes can hold health details: keep them to what the owner asked for.\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/ClientQuery"
          },
          {
            "name": "sort",
            "in": "query",
            "description": "Without `q`.",
            "schema": {
              "type": "string",
              "enum": [
                "recent",
                "name",
                "no_shows"
              ],
              "default": "recent"
            }
          },
          {
            "$ref": "#/components/parameters/Page"
          },
          {
            "name": "per_page",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of clients.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "clients",
                    "pagination"
                  ],
                  "properties": {
                    "clients": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ClientSummary"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          }
        }
      },
      "post": {
        "operationId": "createCalendarClient",
        "tags": [
          "Calendar"
        ],
        "summary": "Add a client",
        "description": "The front desk and admins. Only `name` is required; a phone number another client of this calendar has is a `422`. Limit: 30 changes a minute per account.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "client"
                ],
                "properties": {
                  "client": {
                    "allOf": [
                      {
                        "$ref": "#/components/schemas/ClientInput"
                      },
                      {
                        "required": [
                          "name"
                        ]
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ClientEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/calendars/{calendar_id}/clients.csv": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CalendarId"
        }
      ],
      "get": {
        "operationId": "exportCalendarClients",
        "tags": [
          "Calendar"
        ],
        "summary": "The clients as a CSV file",
        "description": "Admins only, as on the web (the whole client base in one file). Everyone, or the `q` search; notes and alert notes only with `include_notes=1`.",
        "parameters": [
          {
            "$ref": "#/components/parameters/ClientQuery"
          },
          {
            "name": "include_notes",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Every client, or every match.",
            "content": {
              "text/csv": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          }
        }
      }
    },
    "/calendars/{calendar_id}/clients/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CalendarId"
        },
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "get": {
        "operationId": "getCalendarClient",
        "tags": [
          "Calendar"
        ],
        "summary": "One client, as the client's page shows them",
        "description": "The front desk and admins. Notes, visits, what they paid, upcoming bookings, history (the latest 10; `history=all` for every one) and possible duplicates.",
        "parameters": [
          {
            "name": "history",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "all"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The client.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ClientEnvelope"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          }
        }
      },
      "patch": {
        "operationId": "updateCalendarClient",
        "tags": [
          "Calendar"
        ],
        "summary": "Change a client",
        "description": "The front desk and admins. Partial: omitted fields keep their value. `blocked: true` stops them booking online (the desk still can); `alert_note` shows on every booking of theirs. Limit: 30 changes a minute per account.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "client"
                ],
                "properties": {
                  "client": {
                    "$ref": "#/components/schemas/ClientInput"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The client as they are now.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ClientEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/calendars/{calendar_id}/clients/{client_id}/merge": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CalendarId"
        },
        {
          "$ref": "#/components/parameters/ClientId"
        }
      ],
      "post": {
        "operationId": "mergeCalendarClient",
        "tags": [
          "Calendar"
        ],
        "summary": "Merge a possible duplicate onto the older record",
        "description": "The front desk and admins. A client marked `possible_duplicate_of` another (a public booking whose details clashed with an existing client's) is merged onto the older record: bookings, notes, alert note, a missing phone or email. The answer is the kept client, and `merged`: the record that went and what moved. A client not marked as a possible duplicate is `422 not_a_possible_duplicate`; a merged field that would be too long is `422 validation_failed`, and nothing is merged. Limit: 30 changes a minute per account.\n",
        "responses": {
          "200": {
            "description": "Merged.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "client",
                    "merged"
                  ],
                  "properties": {
                    "client": {
                      "$ref": "#/components/schemas/Client"
                    },
                    "merged": {
                      "type": "object",
                      "required": [
                        "id",
                        "name",
                        "moved"
                      ],
                      "properties": {
                        "id": {
                          "type": "integer"
                        },
                        "name": {
                          "type": "string"
                        },
                        "moved": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          },
                          "examples": [
                            [
                              "2 appointments (Mon 7 Jan, Haircut; Thu 10 Jan, Blow-dry)",
                              "the notes"
                            ]
                          ]
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "operationId": "unlinkCalendarClientDuplicate",
        "tags": [
          "Calendar"
        ],
        "summary": "Unlink a possible duplicate (not the same person)",
        "description": "The front desk and admins. The possible-duplicate link goes; both records stay. Limit: 30 changes a minute per account.\n",
        "responses": {
          "200": {
            "description": "Unlinked.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "client",
                    "unlinked"
                  ],
                  "properties": {
                    "client": {
                      "$ref": "#/components/schemas/Client"
                    },
                    "unlinked": {
                      "description": "The client it was linked to, or null when it wasn't.",
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/NamedRecord"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/calendars/{calendar_id}/takings": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CalendarId"
        }
      ],
      "get": {
        "operationId": "getCalendarTakings",
        "tags": [
          "Calendar"
        ],
        "summary": "What was marked paid over a range of days",
        "description": "Admins only, as on the web. Today by default; `range=week` (Monday to Sunday) or `range=month` for this week or month, or `from` and `to` (local dates, up to 366 days). By day, person and method (advances count as `advance` on the day they arrived), and `unpaid`: finished visits nobody marked paid (`count`, and the 50 most recent).\n",
        "parameters": [
          {
            "$ref": "#/components/parameters/TakingsRange"
          },
          {
            "$ref": "#/components/parameters/TakingsFrom"
          },
          {
            "$ref": "#/components/parameters/TakingsTo"
          }
        ],
        "responses": {
          "200": {
            "description": "The takings.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "takings"
                  ],
                  "properties": {
                    "takings": {
                      "$ref": "#/components/schemas/Takings"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          }
        }
      }
    },
    "/calendars/{calendar_id}/takings.csv": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CalendarId"
        }
      ],
      "get": {
        "operationId": "exportCalendarTakings",
        "tags": [
          "Calendar"
        ],
        "summary": "The paid visits as a CSV file",
        "description": "Admins only. The same range as `GET /calendars/{calendar_id}/takings`.",
        "parameters": [
          {
            "$ref": "#/components/parameters/TakingsRange"
          },
          {
            "$ref": "#/components/parameters/TakingsFrom"
          },
          {
            "$ref": "#/components/parameters/TakingsTo"
          }
        ],
        "responses": {
          "200": {
            "description": "One row per paid visit.",
            "content": {
              "text/csv": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          }
        }
      }
    },
    "/calendars/{calendar_id}/settings": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CalendarId"
        }
      ],
      "get": {
        "operationId": "getCalendarSettings",
        "tags": [
          "Calendar setup"
        ],
        "summary": "The calendar's settings",
        "description": "Admins only. The web's Settings page: the live switch, when clients can book, what they're asked for, cancelling, reminders, the morning summary, the business's details and opening hours, and the private feed of every booking (`feed_url`: anyone with it sees the bookings).\n",
        "responses": {
          "200": {
            "description": "The settings.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "settings"
                  ],
                  "properties": {
                    "settings": {
                      "$ref": "#/components/schemas/CalendarSettings"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          }
        }
      },
      "patch": {
        "operationId": "updateCalendarSettings",
        "tags": [
          "Calendar setup"
        ],
        "summary": "Change the calendar's settings",
        "description": "Admins only. Send what changes; the rest stays. `opening_hours` changes the days named and keeps the others (a personal calendar's hours are its person's: `PUT …/providers/{provider_id}/hours`, else `422 invalid_parameter`). A key that isn't a setting is `400 invalid_parameter`; a value that doesn't save is `422 validation_failed` with `details` per setting. `message` says what people will notice (online booking paused or live and on how many Kards, a new kind of business, prices kept in a new currency). `PUT` is the same. Limit: 30 changes a minute per account.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "settings"
                ],
                "properties": {
                  "settings": {
                    "$ref": "#/components/schemas/CalendarSettingsInput"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The settings as they are now.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "settings"
                  ],
                  "properties": {
                    "settings": {
                      "$ref": "#/components/schemas/CalendarSettings"
                    },
                    "message": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "examples": [
                        "Online booking paused. Book is hidden on 4 Kards. Existing appointments stay. Your booking page asks people to call or message you instead."
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/calendars/{calendar_id}/share_kit": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CalendarId"
        }
      ],
      "get": {
        "operationId": "getCalendarShareKit",
        "tags": [
          "Calendar setup"
        ],
        "summary": "Everything for sharing the booking page",
        "description": "Admins only. Setup's share step: the booking link, the Kards showing Book, links that book one person, a WhatsApp message, the QR code to print, snippets for a website, and the latest 20 single-use links. `booking_url` and the rest are null until the calendar has a Kard to book from.\n",
        "responses": {
          "200": {
            "description": "The share kit.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "share_kit"
                  ],
                  "properties": {
                    "share_kit": {
                      "$ref": "#/components/schemas/ShareKit"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          }
        }
      }
    },
    "/calendars/{calendar_id}/feed_link": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CalendarId"
        }
      ],
      "post": {
        "operationId": "renewCalendarFeedLink",
        "tags": [
          "Calendar setup"
        ],
        "summary": "A new private feed link for the whole calendar",
        "description": "Admins only. For a link that leaked: the old one stops at once, and calendars subscribed to it stop updating until they subscribe again with the new `feed_url`. Limit: 30 changes a minute per account.\n",
        "responses": {
          "201": {
            "description": "The new link.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FeedLink"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/calendars/{calendar_id}/booking_links": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CalendarId"
        }
      ],
      "get": {
        "operationId": "listCalendarBookingLinks",
        "tags": [
          "Calendar setup"
        ],
        "summary": "Single-use booking links, newest first",
        "description": "Admins only. Private links that book one offering (or anything on the menu), optionally with one person, a set number of times, until a date.",
        "parameters": [
          {
            "$ref": "#/components/parameters/Page"
          },
          {
            "$ref": "#/components/parameters/PerPage"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of links.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "booking_links",
                    "pagination"
                  ],
                  "properties": {
                    "booking_links": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/BookingLink"
                      }
                    },
                    "pagination": {
                      "$ref": "#/components/schemas/Pagination"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          }
        }
      },
      "post": {
        "operationId": "createCalendarBookingLink",
        "tags": [
          "Calendar setup"
        ],
        "summary": "Make a single-use booking link",
        "description": "Admins only. Everything is optional: without `offering_id` it books anything on the menu, without `provider_id` anyone, `max_uses` defaults to 1, and without `expires_in_days` it doesn't expire. An offering or person that isn't active on this calendar, or a number out of range, is `422 validation_failed`. Send the `url` to the client. Limit: 30 changes a minute per account.\n",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "booking_link": {
                    "$ref": "#/components/schemas/BookingLinkInput"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Made.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "booking_link"
                  ],
                  "properties": {
                    "booking_link": {
                      "$ref": "#/components/schemas/BookingLink"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/calendars/{calendar_id}/booking_links/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CalendarId"
        },
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "delete": {
        "operationId": "deleteCalendarBookingLink",
        "tags": [
          "Calendar setup"
        ],
        "summary": "Delete a single-use booking link",
        "description": "Admins only. It stops booking at once. Limit: 30 changes a minute per account.",
        "responses": {
          "200": {
            "$ref": "#/components/responses/Removed"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/calendars/{calendar_id}/blocks": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CalendarId"
        }
      ],
      "get": {
        "operationId": "listCalendarBlocks",
        "tags": [
          "Calendar"
        ],
        "summary": "Blocked time and closures over a range of days",
        "description": "Every role (the day and week show them). Breaks, blocked time and time off for one person, and closures of the whole business, overlapping the local dates `from` to `to` (up to 92 days; today and the next 30 when omitted).\n",
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "description": "The first local date (YYYY-MM-DD); today when omitted.",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "description": "The last local date; 30 days after `from` when omitted.",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "provider_id",
            "in": "query",
            "description": "What affects this person (their own blocks and the closures).",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "kind",
            "in": "query",
            "description": "A comma-separated list of break, time_off, blocked, closed.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The blocks, in time order.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "from",
                    "to",
                    "blocks"
                  ],
                  "properties": {
                    "from": {
                      "type": "string",
                      "format": "date"
                    },
                    "to": {
                      "type": "string",
                      "format": "date"
                    },
                    "blocks": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CalendarBlock"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          }
        }
      },
      "post": {
        "operationId": "createCalendarBlock",
        "tags": [
          "Calendar"
        ],
        "summary": "Block time for one person, or close the whole business",
        "description": "The front desk and admins (the day view's \"Block time\"). With a `provider_id`: a break, blocked time or time off for that person; without one (or `\"all\"`): the whole business closed. Say when as the desk's sheet does (a local `date` and `time`, then `duration` in minutes or `\"rest\"` of the day, or `until` a time; today, the next quarter hour and an hour when omitted), or with two instants, `starts_at` and `ends_at`. Clients can't book it online; the desk can still book over it, and visits already inside it stay booked: `still_booked` names them, and `message` says what to do. Limit: 30 changes a minute per account.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "block"
                ],
                "properties": {
                  "block": {
                    "$ref": "#/components/schemas/CalendarBlockInput"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Blocked.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "block",
                    "still_booked",
                    "message"
                  ],
                  "properties": {
                    "block": {
                      "$ref": "#/components/schemas/CalendarBlock"
                    },
                    "still_booked": {
                      "type": "array",
                      "description": "Visits already inside it, still booked.",
                      "items": {
                        "$ref": "#/components/schemas/VisitWithClient"
                      }
                    },
                    "message": {
                      "type": "string",
                      "examples": [
                        "Blocked Priya today, 3:00 – 4:00 PM (Training). Clients can't book that time online. Ananya at 3:00 PM is still booked: move it if needed."
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/calendars/{calendar_id}/blocks/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CalendarId"
        },
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "delete": {
        "operationId": "deleteCalendarBlock",
        "tags": [
          "Calendar"
        ],
        "summary": "Remove blocked time or a closure",
        "description": "The front desk and admins. That time is bookable online again. Limit: 30 changes a minute per account.",
        "responses": {
          "200": {
            "$ref": "#/components/responses/Removed"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/calendars/{calendar_id}/offerings": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CalendarId"
        }
      ],
      "get": {
        "operationId": "listCalendarOfferings",
        "tags": [
          "Calendar setup"
        ],
        "summary": "The menu, in order",
        "description": "Admins only. Everything clients book (Services, Meeting types, Treatments … as `labels.offerings` says), switched-off ones too, in menu order, with who performs each at what price and length.",
        "responses": {
          "200": {
            "description": "The menu.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "offerings"
                  ],
                  "properties": {
                    "offerings": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/MenuOffering"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          }
        }
      },
      "post": {
        "operationId": "createCalendarOffering",
        "tags": [
          "Calendar setup"
        ],
        "summary": "Add something to the menu",
        "description": "Admins only, with the web form's rules and words. `name` and `duration_minutes` (steps of 5, up to 12 hours) are needed, and `fixed` and `from` prices need `price_cents`. Without `providers`, everyone on the team performs it. Problems are `422 validation_failed`, with `details` per field (a provider not on this calendar's team is one). Limit: 30 changes a minute per account.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "offering"
                ],
                "properties": {
                  "offering": {
                    "allOf": [
                      {
                        "$ref": "#/components/schemas/OfferingInput"
                      },
                      {
                        "required": [
                          "name",
                          "duration_minutes"
                        ]
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Added.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MenuOfferingEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/calendars/{calendar_id}/offerings/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CalendarId"
        },
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "get": {
        "operationId": "getCalendarOffering",
        "tags": [
          "Calendar setup"
        ],
        "summary": "One offering, with its bookings so far",
        "description": "Admins only. As the web's form shows it, plus `upcoming_bookings`, `recent_bookings` (the last 90 days) and `has_bookings` (then it can be switched off, but not deleted).",
        "responses": {
          "200": {
            "description": "The offering.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MenuOfferingEnvelope"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          }
        }
      },
      "patch": {
        "operationId": "updateCalendarOffering",
        "tags": [
          "Calendar setup"
        ],
        "summary": "Change an offering, or switch it off or on",
        "description": "Admins only. Keys you leave out keep their value; `null` clears one. `questions` and `providers` replace the whole list. `\"active\": false` switches it off: it leaves the booking page and its bookings stay; `true` switches it back on, and the answer carries a `message` to relay. `PUT` is the same. Limit: 30 changes a minute per account.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "offering"
                ],
                "properties": {
                  "offering": {
                    "$ref": "#/components/schemas/OfferingInput"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The offering as it is now.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MenuOfferingEnvelope"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "operationId": "deleteCalendarOffering",
        "tags": [
          "Calendar setup"
        ],
        "summary": "Delete an offering that was never booked",
        "description": "Admins only. One with bookings is `422 has_bookings`: it stays in the history, so switch it off instead (`\"active\": false`). Limit: 30 changes a minute per account.",
        "responses": {
          "200": {
            "$ref": "#/components/responses/Removed"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/calendars/{calendar_id}/offerings/{id}/move": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CalendarId"
        },
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "moveCalendarOffering",
        "tags": [
          "Calendar setup"
        ],
        "summary": "Move an offering up or down within its category",
        "description": "Admins only. Swaps it with its neighbour in the same category. `moved` is false when it's already at that end. Limit: 30 changes a minute per account.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MoveInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The whole menu in its new order.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "moved",
                    "offerings"
                  ],
                  "properties": {
                    "moved": {
                      "type": "boolean"
                    },
                    "offerings": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/MenuOffering"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/calendars/{calendar_id}/menu_imports": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CalendarId"
        }
      ],
      "get": {
        "operationId": "listCalendarMenuImports",
        "tags": [
          "Calendar setup"
        ],
        "summary": "Price-list imports, and whether one can start now",
        "description": "Admins only. A printed price list (a photo, a PDF, a link to one, or its text) is read in the background into draft rows, which you check and then confirm; nothing joins the menu before that. Up to 10 imports a day per calendar (the web's count too). This says whether one can start now (`can_start`, and `refusal` in words when not), the one being read (`running`), one read and waiting for review, and the last 10.\n",
        "responses": {
          "200": {
            "description": "Where imports stand.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "daily_limit",
                    "imports_left_today",
                    "reading_paused",
                    "can_start",
                    "refusal",
                    "running",
                    "waiting_for_review",
                    "menu_imports"
                  ],
                  "properties": {
                    "daily_limit": {
                      "type": "integer"
                    },
                    "imports_left_today": {
                      "type": "integer"
                    },
                    "reading_paused": {
                      "type": "boolean",
                      "description": "The reading service is out for today."
                    },
                    "can_start": {
                      "type": "boolean"
                    },
                    "refusal": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "running": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/MenuImport"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "waiting_for_review": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/MenuImport"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "menu_imports": {
                      "type": "array",
                      "description": "The last 10, newest first (without their rows).",
                      "items": {
                        "$ref": "#/components/schemas/MenuImport"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          }
        }
      },
      "post": {
        "operationId": "createCalendarMenuImport",
        "tags": [
          "Calendar setup"
        ],
        "summary": "Send a price list to be read",
        "description": "Admins only. One of `text`, `image_url` (a public `https://` address leading straight to a photo or PDF, fetched while the list is read: up to 3 redirects, 15 seconds, 15 MB) or a multipart upload with the photo or PDF as `file` (15 MB at most). The answer is `202` with the import `queued`: poll `GET …/menu_imports/{id}` every few seconds (a text takes 10–20 seconds, a photo 20–60). The photo, PDF or text is deleted once the list is added or discarded, or after 24 hours.\n\nRefusals: `409 import_running` (one is still being read; `details.menu_import_id`), `429 rate_limited` (the day's 10 are used; `Retry-After`), `503 reading_paused`, and `422 validation_failed` (nothing sent, more than one, or an address that can't be used).\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "menu_import"
                ],
                "properties": {
                  "menu_import": {
                    "type": "object",
                    "properties": {
                      "text": {
                        "type": "string",
                        "examples": [
                          "Haircut 45 min 500\nBlow-dry 30 min 400"
                        ]
                      },
                      "image_url": {
                        "type": "string",
                        "format": "uri"
                      }
                    }
                  }
                }
              }
            },
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file"
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "contentMediaType": "application/octet-stream",
                    "description": "The photo or PDF."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Queued to be read.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MenuImportEnvelope"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          },
          "409": {
            "$ref": "#/components/responses/ImportRunning"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ReadingPaused"
          }
        }
      }
    },
    "/calendars/{calendar_id}/menu_imports/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CalendarId"
        },
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "get": {
        "operationId": "getCalendarMenuImport",
        "tags": [
          "Calendar setup"
        ],
        "summary": "One price-list import, with its rows once read",
        "description": "Admins only. `status` says where it is. A failed (or `stalled`) one carries `error`, in the web's words, with what to do next. A `ready` one carries the review table: `rows` (the draft as read; `duplicate` rows and those to check are left out by default), its `summary`, and `currency_note` when the list's prices are in another currency (amounts are copied as written, never converted). A `confirmed` one says how many were added.\n",
        "responses": {
          "200": {
            "description": "The import.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MenuImportEnvelope"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          }
        }
      },
      "delete": {
        "operationId": "discardCalendarMenuImport",
        "tags": [
          "Calendar setup"
        ],
        "summary": "Discard a price-list import",
        "description": "Admins only. Nothing is added, and what was sent is deleted. A confirmed or expired one is `409 already_finished`. Limit: 30 changes a minute per account.",
        "responses": {
          "200": {
            "description": "Discarded.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "menu_import",
                    "message"
                  ],
                  "properties": {
                    "menu_import": {
                      "$ref": "#/components/schemas/MenuImport"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          },
          "409": {
            "$ref": "#/components/responses/ImportFinished"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/calendars/{calendar_id}/menu_imports/{id}/confirm": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CalendarId"
        },
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "confirmCalendarMenuImport",
        "tags": [
          "Calendar setup"
        ],
        "summary": "Add a read price list to the menu",
        "description": "Admins only. Everyone on the team performs the new offerings. Without a body, the draft is added as read (the rows `included`). With `rows`, only the rows sent are added (`\"include\": false` leaves one out), each keeping the draft's value for what you leave out. Problems are `422 validation_failed` with `details.rows` (by row key) and `details.base`, and nothing is added. Not read yet, or it failed: `409 not_ready`; already added, discarded or expired: `409 already_finished`. Limit: 30 changes a minute per account.\n",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "rows": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "required": [
                        "key"
                      ],
                      "properties": {
                        "key": {
                          "type": "string"
                        },
                        "include": {
                          "type": "boolean"
                        },
                        "name": {
                          "type": "string"
                        },
                        "category": {
                          "type": "string"
                        },
                        "duration_minutes": {
                          "type": "integer"
                        },
                        "price_cents": {
                          "type": "integer"
                        },
                        "price_mode": {
                          "type": "string",
                          "enum": [
                            "fixed",
                            "from",
                            "free",
                            "hidden"
                          ]
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Added.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "menu_import",
                    "offerings",
                    "message"
                  ],
                  "properties": {
                    "menu_import": {
                      "$ref": "#/components/schemas/MenuImport"
                    },
                    "offerings": {
                      "type": "array",
                      "description": "The new offerings.",
                      "items": {
                        "$ref": "#/components/schemas/MenuOffering"
                      }
                    },
                    "message": {
                      "type": "string",
                      "examples": [
                        "Added 12 services from your price list."
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          },
          "409": {
            "description": "Not read yet, or it failed (`not_ready`); or already added, discarded or expired (`already_finished`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/calendars/{calendar_id}/menu_imports/{id}/source": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CalendarId"
        },
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "get": {
        "operationId": "getCalendarMenuImportSource",
        "tags": [
          "Calendar setup"
        ],
        "summary": "The photo or PDF of a price list",
        "description": "Admins only. The file itself, to check the rows against. A pasted text, a link not fetched yet, or a file already deleted is `404 no_source`.",
        "responses": {
          "200": {
            "description": "The file, as it was sent.",
            "content": {
              "image/*": {
                "schema": {
                  "type": "string",
                  "contentMediaType": "image/*"
                }
              },
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "contentMediaType": "application/pdf"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "description": "No such import (`not_found`), or no file to send (`no_source`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/calendars/{calendar_id}/menu_imports/{id}/reread": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CalendarId"
        },
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "rereadCalendarMenuImport",
        "tags": [
          "Calendar setup"
        ],
        "summary": "Read a price list again after the reading service failed",
        "description": "Admins only, and only after the reading service itself failed (`error.code` `provider` or `budget`): the same list is read again as a new import (`202`; the old one is discarded), with the same refusals as starting one. Anything else is `409 not_rereadable`. Limit: 30 changes a minute per account.\n",
        "responses": {
          "202": {
            "description": "The new import, queued.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MenuImportEnvelope"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          },
          "409": {
            "description": "Not one the reading service failed on (`not_rereadable`), or another is still being read (`import_running`).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/ReadingPaused"
          }
        }
      }
    },
    "/calendars/{calendar_id}/providers": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CalendarId"
        }
      ],
      "get": {
        "operationId": "listCalendarProviders",
        "tags": [
          "Calendar setup"
        ],
        "summary": "The team, in order, and team Kards not on it yet",
        "description": "Admins only. Everyone who takes bookings (Team members, Hosts, Stylists … as `labels.providers` says), switched-off people too, in the team's order, with what they do and where their login stands. Nobody needs an account to be on the team. `suggested_kards`: the organization's Kards nobody stands behind yet, each one call from joining (`POST …/providers` with `{\"provider\": {\"kard\": \"<share_link>\"}}`).\n",
        "responses": {
          "200": {
            "description": "The team.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "providers",
                    "suggested_kards"
                  ],
                  "properties": {
                    "providers": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/TeamProvider"
                      }
                    },
                    "suggested_kards": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/SuggestedKard"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          }
        }
      },
      "post": {
        "operationId": "createCalendarProvider",
        "tags": [
          "Calendar setup"
        ],
        "summary": "Add someone to the team",
        "description": "Admins only. Three ways:\n\n- By name: only `name` is needed. They start with the opening hours and do everything on the menu unless `offering_ids` says what; `color` is the next free one if left out.\n- From a team Kard in one call: `{\"kard\": \"priya\"}` with no `name` takes the Kard's name, tagline and number, links it, and does everything.\n- Yourself: `{\"me\": true}` puts the key's own person on the team, linked to their account (organization members). Already on it: `200` with your record. A personal calendar, or someone outside the organization, is `422 cannot_join`, saying why.\n\nA `kard` must be one of the business's Kards that nobody else stands behind, and not the business's own Kard (that one books the whole team): otherwise `422 validation_failed` with `details.kard`. Limit: 30 changes a minute per account.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "provider"
                ],
                "properties": {
                  "provider": {
                    "allOf": [
                      {
                        "$ref": "#/components/schemas/ProviderInput"
                      },
                      {
                        "type": "object",
                        "properties": {
                          "me": {
                            "type": "boolean",
                            "description": "Put the key's own person on the team."
                          }
                        }
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/TeamProvider"
          },
          "201": {
            "$ref": "#/components/responses/TeamProvider"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/calendars/{calendar_id}/providers/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CalendarId"
        },
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "get": {
        "operationId": "getCalendarProvider",
        "tags": [
          "Calendar setup"
        ],
        "summary": "One team member, as their page shows them",
        "description": "Admins, and that person's own login (a front-desk or staff login on its `your_provider`; without `login`). Their record plus their week, upcoming special days and time off, and their private links (`links`: anyone with them sees this person's bookings).\n",
        "responses": {
          "200": {
            "description": "The person.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "provider"
                  ],
                  "properties": {
                    "provider": {
                      "$ref": "#/components/schemas/TeamProviderDetail"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          }
        }
      },
      "patch": {
        "operationId": "updateCalendarProvider",
        "tags": [
          "Calendar setup"
        ],
        "summary": "Change a team member, or switch them off or on",
        "description": "Admins only. Keys left out keep their value. `\"kard\": null` unlinks their Kard; `offering_ids` replaces what they do (what they keep keeps their own price and minutes, set in `providers` of `PATCH …/offerings/{id}`). `\"active\": false` switches them off: they leave the calendar and the booking page, their past bookings stay, and their \"My day\" and calendar links stop working (the `message` says so). `PUT` is the same. Limit: 30 changes a minute per account.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "provider"
                ],
                "properties": {
                  "provider": {
                    "$ref": "#/components/schemas/ProviderInput"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/TeamProvider"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "operationId": "deleteCalendarProvider",
        "tags": [
          "Calendar setup"
        ],
        "summary": "Take someone off the team",
        "description": "Admins only, with their hours and time off. Someone with bookings stays in the history: `422 has_bookings` (switch them off with `\"active\": false` instead). The only person on a personal calendar stays: `422 last_provider` (pause online booking in settings instead). Limit: 30 changes a minute per account.\n",
        "responses": {
          "200": {
            "$ref": "#/components/responses/Removed"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/calendars/{calendar_id}/providers/{id}/move": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CalendarId"
        },
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "moveCalendarProvider",
        "tags": [
          "Calendar setup"
        ],
        "summary": "Move someone up or down the team",
        "description": "Admins only. The order of the day's columns, and who comes first when a client lets the business choose. `moved` is false (with a `message`) when they're already first or last. Limit: 30 changes a minute per account.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MoveInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The team in its new order.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "moved",
                    "providers"
                  ],
                  "properties": {
                    "moved": {
                      "type": "boolean"
                    },
                    "message": {
                      "type": "string",
                      "examples": [
                        "Priya Sharma is already first on the team."
                      ]
                    },
                    "providers": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/TeamProvider"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/calendars/{calendar_id}/providers/{provider_id}/hours": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CalendarId"
        },
        {
          "$ref": "#/components/parameters/ProviderId"
        }
      ],
      "get": {
        "operationId": "getCalendarProviderHours",
        "tags": [
          "Calendar setup"
        ],
        "summary": "A team member's weekly hours",
        "description": "Admins, and that person's own login. `[]` is a day off; two ranges are a split shift or a lunch break. `opening_hours` are the business's, to copy.",
        "responses": {
          "200": {
            "description": "Their hours.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "hours"
                  ],
                  "properties": {
                    "hours": {
                      "$ref": "#/components/schemas/ProviderHours"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          }
        }
      },
      "patch": {
        "operationId": "updateCalendarProviderHours",
        "tags": [
          "Calendar setup"
        ],
        "summary": "Change a team member's weekly hours",
        "description": "Admins, and that person's own login. The days named change; the rest keep their hours. Existing bookings stay where they are (the `message` says so). Problems are `422 validation_failed` with `details.weekly_hours` (\"Monday: 10:00–12:00 overlaps 11:00–13:00\"), and nothing is saved. `bookable_online` (whether clients can pick them online) is the admins' switch: from anyone else it's `403 forbidden`, and nothing is saved. `PUT` is the same. Limit: 30 changes a minute per account.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "weekly_hours": {
                    "$ref": "#/components/schemas/WeeklyHoursInput"
                  },
                  "bookable_online": {
                    "type": "boolean",
                    "description": "Admins only."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Their hours as they are now.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "hours",
                    "message"
                  ],
                  "properties": {
                    "hours": {
                      "$ref": "#/components/schemas/ProviderHours"
                    },
                    "message": {
                      "type": "string",
                      "examples": [
                        "Saved Priya's hours. Existing appointments stay where they are."
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/calendars/{calendar_id}/providers/{provider_id}/special_days": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CalendarId"
        },
        {
          "$ref": "#/components/parameters/ProviderId"
        }
      ],
      "get": {
        "operationId": "listCalendarProviderSpecialDays",
        "tags": [
          "Calendar setup"
        ],
        "summary": "A team member's upcoming special days",
        "description": "Admins, and that person's own login. Dates with different hours, or a day off, from the calendar's today; they replace the weekly hours that day.",
        "responses": {
          "200": {
            "description": "Their special days.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "special_days"
                  ],
                  "properties": {
                    "special_days": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/SpecialDay"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          }
        }
      },
      "post": {
        "operationId": "createCalendarProviderSpecialDay",
        "tags": [
          "Calendar setup"
        ],
        "summary": "Set different hours on one date, or a day off",
        "description": "Admins, and that person's own login. A date that already has special hours is replaced: `201` new, `200` replaced. No `date`, no `hours` (without `day_off`), or hours that overlap or end before they start: `422 validation_failed` (`details.date`, `details.hours`). A date that isn't `YYYY-MM-DD` is `400 invalid_parameter`. Limit: 30 changes a minute per account.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "special_day"
                ],
                "properties": {
                  "special_day": {
                    "$ref": "#/components/schemas/SpecialDayInput"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/SpecialDaySaved"
          },
          "201": {
            "$ref": "#/components/responses/SpecialDaySaved"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/calendars/{calendar_id}/providers/{provider_id}/special_days/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CalendarId"
        },
        {
          "$ref": "#/components/parameters/ProviderId"
        },
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "The special day's id, or its date (YYYY-MM-DD).",
          "schema": {
            "type": "string",
            "examples": [
              "1018409891",
              "2030-01-10"
            ]
          }
        }
      ],
      "delete": {
        "operationId": "deleteCalendarProviderSpecialDay",
        "tags": [
          "Calendar setup"
        ],
        "summary": "Put a date back to the usual hours",
        "description": "Admins, and that person's own login. By the special day's id or its date. Limit: 30 changes a minute per account.",
        "responses": {
          "200": {
            "$ref": "#/components/responses/Removed"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/calendars/{calendar_id}/providers/{provider_id}/time_off": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CalendarId"
        },
        {
          "$ref": "#/components/parameters/ProviderId"
        }
      ],
      "get": {
        "operationId": "listCalendarProviderTimeOff",
        "tags": [
          "Calendar setup"
        ],
        "summary": "A team member's upcoming time off, breaks and blocked time",
        "description": "Admins, and that person's own login. Everything ending from now on. `bookings_to_move`: upcoming bookings with this person inside it, which need moving (clients can't book this time; the front desk can, after a warning).",
        "responses": {
          "200": {
            "description": "Their time off.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "time_off"
                  ],
                  "properties": {
                    "time_off": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/TimeOff"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          }
        }
      },
      "post": {
        "operationId": "createCalendarProviderTimeOff",
        "tags": [
          "Calendar setup"
        ],
        "summary": "Add time off, a break or blocked time for a team member",
        "description": "Admins, and that person's own login. Whole days (`starts_on`, `ends_on`, `all_day: true`), or a few hours (`starts_on`, `start_time`, `end_time`). `ends_on` left out is the same day; `all_day` left out is all day unless times are given; `kind` left out is `time_off`. The `message` counts the bookings to move. Problems are `422 validation_failed` with the form's sentences in `details.base` (\"Pick an end time.\"). Limit: 30 changes a minute per account.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "time_off"
                ],
                "properties": {
                  "time_off": {
                    "$ref": "#/components/schemas/TimeOffInput"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Added.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "time_off",
                    "message"
                  ],
                  "properties": {
                    "time_off": {
                      "$ref": "#/components/schemas/TimeOff"
                    },
                    "message": {
                      "type": "string",
                      "examples": [
                        "Added: Mon 14 Jan – Tue 15 Jan (time off). Clients can't book it."
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/calendars/{calendar_id}/providers/{provider_id}/time_off/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CalendarId"
        },
        {
          "$ref": "#/components/parameters/ProviderId"
        },
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "delete": {
        "operationId": "deleteCalendarProviderTimeOff",
        "tags": [
          "Calendar setup"
        ],
        "summary": "Remove a team member's time off, break or blocked time",
        "description": "Admins, and that person's own login. Their own blocks only: that time is bookable again. A closure of the whole calendar is a `404` here; remove it with `DELETE /calendars/{calendar_id}/blocks/{id}`. Limit: 30 changes a minute per account.",
        "responses": {
          "200": {
            "$ref": "#/components/responses/Removed"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/calendars/{calendar_id}/providers/{provider_id}/my_day_link": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CalendarId"
        },
        {
          "$ref": "#/components/parameters/ProviderId"
        }
      ],
      "post": {
        "operationId": "renewCalendarProviderMyDayLink",
        "tags": [
          "Calendar setup"
        ],
        "summary": "A new private \"My day\" link for a team member",
        "description": "Admins, and that person's own login. Their day on a phone, no login. The old link stops opening at once (a lost phone, someone who left): send them the new one. Their calendar feed keeps working. Limit: 30 changes a minute per account.",
        "responses": {
          "201": {
            "description": "The new link.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "my_day_url",
                    "message"
                  ],
                  "properties": {
                    "my_day_url": {
                      "type": "string",
                      "format": "uri"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/calendars/{calendar_id}/providers/{provider_id}/feed_link": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CalendarId"
        },
        {
          "$ref": "#/components/parameters/ProviderId"
        }
      ],
      "post": {
        "operationId": "renewCalendarProviderFeedLink",
        "tags": [
          "Calendar setup"
        ],
        "summary": "A new private calendar-feed link for a team member",
        "description": "Admins, and that person's own login. Calendars subscribed to the old link stop updating until they subscribe again with the new one. \"My day\" keeps working. Limit: 30 changes a minute per account.",
        "responses": {
          "201": {
            "description": "The new link.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FeedLink"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/calendars/{calendar_id}/logins": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CalendarId"
        }
      ],
      "get": {
        "operationId": "listCalendarLogins",
        "tags": [
          "Calendar setup"
        ],
        "summary": "Who signs in to the calendar",
        "description": "Admins only, on an organization's calendar (a personal calendar is a `404`: only its owner signs in). The organization's admins, the front-desk logins and their open invitations, each team member and where their login stands, what each role can do (`roles`, in words) and a `summary` line.\n",
        "responses": {
          "200": {
            "description": "The logins.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "admins",
                    "front_desk",
                    "providers",
                    "roles",
                    "summary"
                  ],
                  "properties": {
                    "admins": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/LoginPerson"
                      }
                    },
                    "front_desk": {
                      "type": "object",
                      "required": [
                        "logins",
                        "invitations"
                      ],
                      "properties": {
                        "logins": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/CalendarLogin"
                          }
                        },
                        "invitations": {
                          "type": "array",
                          "description": "Open front-desk invitations not tied to a team member.",
                          "items": {
                            "$ref": "#/components/schemas/StaffInvitation"
                          }
                        }
                      }
                    },
                    "providers": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": [
                          "provider",
                          "login"
                        ],
                        "properties": {
                          "provider": {
                            "type": "object",
                            "required": [
                              "id",
                              "name",
                              "title",
                              "active"
                            ],
                            "properties": {
                              "id": {
                                "type": "integer"
                              },
                              "name": {
                                "type": "string"
                              },
                              "title": {
                                "type": [
                                  "string",
                                  "null"
                                ]
                              },
                              "active": {
                                "type": "boolean"
                              }
                            }
                          },
                          "login": {
                            "type": "object",
                            "required": [
                              "status",
                              "id",
                              "role",
                              "user",
                              "invitation"
                            ],
                            "properties": {
                              "status": {
                                "$ref": "#/components/schemas/LoginStatus"
                              },
                              "id": {
                                "type": [
                                  "integer",
                                  "null"
                                ],
                                "description": "The login's id, for DELETE …/logins/{id} (joined only)."
                              },
                              "role": {
                                "type": [
                                  "string",
                                  "null"
                                ],
                                "enum": [
                                  "front_desk",
                                  "staff",
                                  null
                                ]
                              },
                              "user": {
                                "oneOf": [
                                  {
                                    "$ref": "#/components/schemas/LoginPerson"
                                  },
                                  {
                                    "type": "null"
                                  }
                                ]
                              },
                              "invitation": {
                                "oneOf": [
                                  {
                                    "$ref": "#/components/schemas/StaffInvitation"
                                  },
                                  {
                                    "type": "null"
                                  }
                                ]
                              }
                            }
                          }
                        }
                      }
                    },
                    "roles": {
                      "type": "object",
                      "required": [
                        "front_desk",
                        "staff"
                      ],
                      "properties": {
                        "front_desk": {
                          "type": "string"
                        },
                        "staff": {
                          "type": "string"
                        }
                      }
                    },
                    "summary": {
                      "type": "string",
                      "examples": [
                        "4 people sign in to Bloom Salon's calendar: 2 admins, 1 front-desk login and 1 team member."
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          }
        }
      }
    },
    "/calendars/{calendar_id}/logins/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CalendarId"
        },
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "delete": {
        "operationId": "removeCalendarLogin",
        "tags": [
          "Calendar setup"
        ],
        "summary": "Remove a front-desk or staff login's access",
        "description": "Admins only, on an organization's calendar. Bookings, hours and time off are unchanged. Limit: 30 changes a minute per account.",
        "responses": {
          "200": {
            "$ref": "#/components/responses/Removed"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/calendars/{calendar_id}/invitations": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CalendarId"
        }
      ],
      "get": {
        "operationId": "listCalendarInvitations",
        "tags": [
          "Calendar setup"
        ],
        "summary": "Open front-desk and staff invitations",
        "description": "Admins only, on an organization's calendar. Newest first; one past its expiry stays open until resent or cancelled (`status` says `expired`).",
        "responses": {
          "200": {
            "description": "The invitations.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "invitations"
                  ],
                  "properties": {
                    "invitations": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/StaffInvitation"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          }
        }
      },
      "post": {
        "operationId": "createCalendarInvitation",
        "tags": [
          "Calendar setup"
        ],
        "summary": "Invite someone to sign in, as front desk or as a team member",
        "description": "Admins only, on an organization's calendar. The invitation is emailed at once. `role` `staff` signs in as the team member `provider_id` names; `front_desk` runs the desk. Problems are `422 validation_failed`. Limits: 30 changes a minute per account, and 30 invitations an hour (the web's count too).\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "invitation"
                ],
                "properties": {
                  "invitation": {
                    "type": "object",
                    "required": [
                      "email"
                    ],
                    "properties": {
                      "email": {
                        "type": "string",
                        "format": "email"
                      },
                      "role": {
                        "type": "string",
                        "enum": [
                          "front_desk",
                          "staff"
                        ]
                      },
                      "provider_id": {
                        "type": "integer",
                        "description": "The team member a staff login signs in as."
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "$ref": "#/components/responses/InvitationNow"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/calendars/{calendar_id}/invitations/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CalendarId"
        },
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "delete": {
        "operationId": "cancelCalendarInvitation",
        "tags": [
          "Calendar setup"
        ],
        "summary": "Cancel an open invitation",
        "description": "Admins only, on an organization's calendar. Its link stops working; the answer is the invitation, now revoked. Limit: 30 changes a minute per account.",
        "responses": {
          "200": {
            "$ref": "#/components/responses/InvitationNow"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/calendars/{calendar_id}/invitations/{id}/resend": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CalendarId"
        },
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "resendCalendarInvitation",
        "tags": [
          "Calendar setup"
        ],
        "summary": "Send an open invitation again",
        "description": "Admins only, on an organization's calendar. Its link works for another two weeks. One sent a moment ago isn't sent again: `sent` is false, and the `message` says so. Limit: 30 changes a minute per account.",
        "responses": {
          "200": {
            "description": "The invitation, and whether it went.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "invitation",
                    "sent",
                    "message"
                  ],
                  "properties": {
                    "invitation": {
                      "$ref": "#/components/schemas/StaffInvitation"
                    },
                    "sent": {
                      "type": "boolean"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/calendars/{calendar_id}/webhooks": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CalendarId"
        }
      ],
      "get": {
        "operationId": "listCalendarWebhooks",
        "tags": [
          "Calendar setup"
        ],
        "summary": "Webhook endpoints, and the events they can get",
        "description": "Admins only. Up to `max` endpoints get the calendar's booking changes (see the `booking` webhook), each with its signing secret, its latest delivery and its latest test.",
        "responses": {
          "200": {
            "description": "The endpoints.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "webhooks",
                    "events",
                    "max"
                  ],
                  "properties": {
                    "webhooks": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CalendarWebhook"
                      }
                    },
                    "events": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/WebhookEvent"
                      }
                    },
                    "max": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          }
        }
      },
      "post": {
        "operationId": "createCalendarWebhook",
        "tags": [
          "Calendar setup"
        ],
        "summary": "Add a webhook endpoint",
        "description": "Admins only. Every event unless `events` names some. Send a test (`POST …/webhooks/{id}/test`) to check it answers. An address that can't be used, or a sixth endpoint, is `422 validation_failed`. Limit: 30 changes a minute per account.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "webhook"
                ],
                "properties": {
                  "webhook": {
                    "allOf": [
                      {
                        "$ref": "#/components/schemas/WebhookInput"
                      },
                      {
                        "required": [
                          "url"
                        ]
                      }
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "$ref": "#/components/responses/WebhookNow"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/calendars/{calendar_id}/webhooks/{id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CalendarId"
        },
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "patch": {
        "operationId": "updateCalendarWebhook",
        "tags": [
          "Calendar setup"
        ],
        "summary": "Change a webhook endpoint, or switch it back on",
        "description": "Admins only. `enabled: true` switches on an endpoint that was turned off after failing. `PUT` is the same. Limit: 30 changes a minute per account.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "webhook"
                ],
                "properties": {
                  "webhook": {
                    "$ref": "#/components/schemas/WebhookInput"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/WebhookNow"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "operationId": "deleteCalendarWebhook",
        "tags": [
          "Calendar setup"
        ],
        "summary": "Remove a webhook endpoint",
        "description": "Admins only. Nothing more is sent to it. Limit: 30 changes a minute per account.",
        "responses": {
          "200": {
            "$ref": "#/components/responses/Removed"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/calendars/{calendar_id}/webhooks/{id}/test": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CalendarId"
        },
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "testCalendarWebhook",
        "tags": [
          "Calendar setup"
        ],
        "summary": "Send a webhook endpoint a test event",
        "description": "Admins only. A `booking.created` event with made-up client details (`test: true`), sent in the background: `202`, and its answer shows in the endpoint's `test` (`GET …/webhooks`) within a few seconds. While one is still sending, the answer is `200` and nothing more is sent. Limits: 30 changes a minute per account, and 10 tests a minute (the web's count too).\n",
        "responses": {
          "200": {
            "$ref": "#/components/responses/WebhookNow"
          },
          "202": {
            "$ref": "#/components/responses/WebhookNow"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/calendars/{calendar_id}/webhooks/{id}/rotate_secret": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CalendarId"
        },
        {
          "$ref": "#/components/parameters/Id"
        }
      ],
      "post": {
        "operationId": "rotateCalendarWebhookSecret",
        "tags": [
          "Calendar setup"
        ],
        "summary": "Give a webhook endpoint a new signing secret",
        "description": "Admins only. The old secret stops at once: update the receiver with the new `secret`. Limit: 30 changes a minute per account.",
        "responses": {
          "200": {
            "$ref": "#/components/responses/WebhookNow"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/calendars/{calendar_id}/clients/import": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CalendarId"
        }
      ],
      "get": {
        "operationId": "getCalendarClientImportSources",
        "tags": [
          "Calendar setup"
        ],
        "summary": "Where clients can be imported from",
        "description": "Admins only. A spreadsheet (CSV: its limits, and the fields a column can be), or the people who sent their details through the business's Kards (`contacts`: how many, by Kard).",
        "responses": {
          "200": {
            "description": "The sources.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "csv",
                    "contacts"
                  ],
                  "properties": {
                    "csv": {
                      "type": "object",
                      "required": [
                        "max_bytes",
                        "max_rows",
                        "fields",
                        "default_country"
                      ],
                      "properties": {
                        "max_bytes": {
                          "type": "integer"
                        },
                        "max_rows": {
                          "type": "integer"
                        },
                        "fields": {
                          "type": "object",
                          "description": "What a column can be, with its label.",
                          "additionalProperties": {
                            "type": "string"
                          }
                        },
                        "default_country": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "description": "Reads local phone numbers; send `country` to use another."
                        }
                      }
                    },
                    "contacts": {
                      "type": "object",
                      "required": [
                        "total",
                        "kards"
                      ],
                      "properties": {
                        "total": {
                          "type": "integer"
                        },
                        "kards": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "required": [
                              "id",
                              "share_link",
                              "name",
                              "contacts"
                            ],
                            "properties": {
                              "id": {
                                "type": "integer"
                              },
                              "share_link": {
                                "type": "string"
                              },
                              "name": {
                                "type": "string"
                              },
                              "contacts": {
                                "type": "integer"
                              }
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          }
        }
      },
      "post": {
        "operationId": "importCalendarClients",
        "tags": [
          "Calendar setup"
        ],
        "summary": "Import clients from a spreadsheet or the Kards' contacts, or preview it",
        "description": "Admins only. `source: \"csv\"` (the default) takes the spreadsheet's text as `csv`, or the file as `file` (multipart); its columns are guessed unless `mapping` says what each one is. `source: \"contacts\"` takes the people on the Kards in `kard_ids` (all of them when omitted). Send `dry_run: true` first: the `preview` says what each row would do (create, update a client's blanks, or skip, with the reason) and what still needs choosing (`problems`). Without it, the import runs: `result` and a `message`. Nobody is messaged.\n\nProblems are `422 validation_failed` (`details.mapping` or `details.kard_ids`), `422 too_many_rows`, `422 unreadable` (a file that isn't a CSV), and `409 changed_meanwhile` (a client was added meanwhile: preview again). Limit: 30 changes a minute per account.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ClientImportInput"
              }
            },
            "multipart/form-data": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/ClientImportInput"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "file": {
                        "type": "string",
                        "contentMediaType": "text/csv"
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The preview (`dry_run`), or the import's result.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "type": "object",
                      "required": [
                        "preview"
                      ],
                      "properties": {
                        "preview": {
                          "$ref": "#/components/schemas/ClientImportPreview"
                        }
                      }
                    },
                    {
                      "type": "object",
                      "required": [
                        "result",
                        "message"
                      ],
                      "properties": {
                        "result": {
                          "$ref": "#/components/schemas/ClientImportResult"
                        },
                        "message": {
                          "type": "string",
                          "examples": [
                            "12 new clients added, 3 updated. Nobody was messaged."
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/CalendarForbidden"
          },
          "404": {
            "$ref": "#/components/responses/CalendarNotFound"
          },
          "409": {
            "description": "A client was written meanwhile (`changed_meanwhile`); preview again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/calendar_sync": {
      "get": {
        "operationId": "getCalendarSync",
        "tags": [
          "Calendar setup"
        ],
        "summary": "The key's own Google Calendar or Outlook connection",
        "description": "Any key, for its own connection only: it belongs to the person, not to a calendar. Busy times in the calendars they tick block their bookable time, and new bookings land in the one they pick, for every team member linked to their account (`providers`). Connecting needs the service's consent screen, so it happens in the browser: send the owner to `connect_url`. `connection` is null until then.\n",
        "responses": {
          "200": {
            "description": "The connection and where it counts.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "connection",
                    "services",
                    "providers",
                    "connect_url"
                  ],
                  "properties": {
                    "connection": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/CalendarConnection"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    },
                    "services": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": [
                          "key",
                          "name",
                          "available"
                        ],
                        "properties": {
                          "key": {
                            "$ref": "#/components/schemas/SyncService"
                          },
                          "name": {
                            "type": "string",
                            "examples": [
                              "Google Calendar",
                              "Outlook"
                            ]
                          },
                          "available": {
                            "type": "boolean",
                            "description": "It can be connected on this site."
                          }
                        }
                      }
                    },
                    "providers": {
                      "type": "array",
                      "description": "The team members linked to this account, where the sync counts.",
                      "items": {
                        "type": "object",
                        "required": [
                          "id",
                          "name",
                          "calendar"
                        ],
                        "properties": {
                          "id": {
                            "type": "integer"
                          },
                          "name": {
                            "type": "string"
                          },
                          "calendar": {
                            "$ref": "#/components/schemas/NamedRecord"
                          }
                        }
                      }
                    },
                    "connect_url": {
                      "type": "string",
                      "format": "uri"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/calendar_sync/{service}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/SyncService"
        }
      ],
      "patch": {
        "operationId": "updateCalendarSync",
        "tags": [
          "Calendar setup"
        ],
        "summary": "Choose the calendars to check, and where bookings go",
        "description": "Any key, its own connection. Partial: omitted keys keep their value. `busy_calendar_ids` (up to 10 of its `calendars`; `[]` checks none), `destination_calendar_id` (`null` stops adding bookings), `add_meet_links` (a Google Meet or Microsoft Teams link on video meetings) and `invite_clients` (clients are added to the event as guests). Not connected: `409 not_connected`, with where to connect. A calendar that isn't the account's is `422 validation_failed`. `PUT` is the same. Limit: 30 changes a minute per account.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "calendar_sync"
                ],
                "properties": {
                  "calendar_sync": {
                    "$ref": "#/components/schemas/CalendarSyncInput"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The connection as it is now.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "connection",
                    "message"
                  ],
                  "properties": {
                    "connection": {
                      "$ref": "#/components/schemas/CalendarConnection"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "409": {
            "$ref": "#/components/responses/NotConnected"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "operationId": "disconnectCalendarSync",
        "tags": [
          "Calendar setup"
        ],
        "summary": "Disconnect Google Calendar or Outlook",
        "description": "Any key, its own connection. Busy times stop blocking, and new bookings stop being added; events already added stay. Limit: 30 changes a minute per account.",
        "responses": {
          "200": {
            "description": "Disconnected.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "connection",
                    "message"
                  ],
                  "properties": {
                    "connection": {
                      "type": "null"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "409": {
            "$ref": "#/components/responses/NotConnected"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/calendar_sync/{service}/add_upcoming": {
      "parameters": [
        {
          "$ref": "#/components/parameters/SyncService"
        }
      ],
      "post": {
        "operationId": "addUpcomingToCalendarSync",
        "tags": [
          "Calendar setup"
        ],
        "summary": "Add upcoming bookings that aren't in the synced calendar yet",
        "description": "Any key, its own connection, while it adds bookings (`writes_events`). Queued in the background: `202` with how many. Not connected, or not adding bookings: `409 not_connected`. Limit: 30 changes a minute per account.",
        "responses": {
          "202": {
            "description": "Queued.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "count",
                    "message"
                  ],
                  "properties": {
                    "count": {
                      "type": "integer"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "409": {
            "$ref": "#/components/responses/NotConnected"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    }
  },
  "webhooks": {
    "booking": {
      "post": {
        "operationId": "bookingWebhook",
        "tags": [
          "Bookings"
        ],
        "summary": "A booking changed",
        "description": "A booking calendar pushes every change to up to 5 endpoints its owner adds on the web (https://ekard.app/calendar/webhooks, or an organization's calendar settings). Headers follow the Standard Webhooks spec (`webhook-id`, `webhook-timestamp`, `webhook-signature: v1,<base64>`): HMAC-SHA256 over `{webhook-id}.{webhook-timestamp}.{raw body}` with the base64-decoded part of the endpoint's `whsec_` secret. Answer any 2xx within 10 seconds; anything else is retried 5 more times over about 15 minutes.\n",
        "parameters": [
          {
            "name": "webhook-id",
            "in": "header",
            "required": true,
            "description": "The same on every retry of one event; use it to skip duplicates.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "webhook-timestamp",
            "in": "header",
            "required": true,
            "description": "Unix seconds of this attempt. Reject timestamps more than 5 minutes from now.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "webhook-signature",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BookingWebhookEvent"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Received (any 2xx counts)."
          }
        },
        "security": []
      }
    }
  },
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "ekard_...",
        "description": "The account's API key, from https://ekard.app/account/ai."
      }
    },
    "parameters": {
      "ShareLink": {
        "name": "share_link",
        "in": "path",
        "required": true,
        "description": "The kard's share_link, its public URL slug.",
        "schema": {
          "type": "string"
        }
      },
      "Organization": {
        "name": "organization",
        "in": "query",
        "description": "An organization's slug, when the same share_link exists in two organizations you belong to.",
        "schema": {
          "type": "string"
        }
      },
      "OrganizationSlug": {
        "name": "slug",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string"
        }
      },
      "Id": {
        "name": "id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "integer"
        }
      },
      "DocumentId": {
        "name": "document_id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "integer"
        }
      },
      "Query": {
        "name": "query",
        "in": "query",
        "description": "Searches name, email, message and custom fields.",
        "schema": {
          "type": "string"
        }
      },
      "ContactSort": {
        "name": "sort",
        "in": "query",
        "schema": {
          "type": "string",
          "enum": [
            "name",
            "email",
            "created_at",
            "message"
          ],
          "default": "created_at"
        }
      },
      "Direction": {
        "name": "direction",
        "in": "query",
        "schema": {
          "type": "string",
          "enum": [
            "asc",
            "desc"
          ],
          "default": "desc"
        }
      },
      "Page": {
        "name": "page",
        "in": "query",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "default": 1
        }
      },
      "PerPage": {
        "name": "per_page",
        "in": "query",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 100,
          "default": 25
        }
      },
      "From": {
        "name": "from",
        "in": "query",
        "description": "ISO 8601; now when omitted.",
        "schema": {
          "type": "string",
          "format": "date-time"
        }
      },
      "To": {
        "name": "to",
        "in": "query",
        "description": "ISO 8601.",
        "schema": {
          "type": "string",
          "format": "date-time"
        }
      },
      "BookingStatuses": {
        "name": "status",
        "in": "query",
        "description": "A comma-separated list of requested, booked, arrived, completed, cancelled, no_show, declined.",
        "schema": {
          "type": "string",
          "default": "requested,booked"
        }
      },
      "CalendarId": {
        "name": "calendar_id",
        "in": "path",
        "required": true,
        "description": "A calendar's id, from GET /calendars. A calendar this key has no role on answers 404, like one that doesn't exist.",
        "schema": {
          "type": "integer"
        }
      },
      "BookingId": {
        "name": "booking_id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "integer"
        }
      },
      "ClientId": {
        "name": "client_id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "integer"
        }
      },
      "CalendarDate": {
        "name": "date",
        "in": "query",
        "description": "A local date (YYYY-MM-DD) in the calendar's zone; today there when omitted.",
        "schema": {
          "type": "string",
          "format": "date"
        }
      },
      "OfferingIds": {
        "name": "offering_ids[]",
        "in": "query",
        "description": "One or more offerings, back to back with one person. Required unless `booking_id` is sent.",
        "schema": {
          "type": "array",
          "items": {
            "type": "integer"
          }
        },
        "style": "form",
        "explode": true
      },
      "AvailabilityMode": {
        "name": "mode",
        "in": "query",
        "description": "`front_desk`: what the business may book, ignoring notice and the booking window. `online`: what clients can book.",
        "schema": {
          "type": "string",
          "enum": [
            "front_desk",
            "online"
          ],
          "default": "front_desk"
        }
      },
      "LockVersion": {
        "name": "lock_version",
        "in": "query",
        "description": "The booking's lock_version, from its JSON. If someone changed the booking since, nothing changes (409 stale_booking).",
        "schema": {
          "type": "integer"
        }
      },
      "ClientQuery": {
        "name": "q",
        "in": "query",
        "description": "Part of a name or email, or any 3+ digits of a phone number.",
        "schema": {
          "type": "string"
        }
      },
      "TakingsRange": {
        "name": "range",
        "in": "query",
        "description": "Today, this week (Monday to Sunday) or this month; instead of `from` and `to`.",
        "schema": {
          "type": "string",
          "enum": [
            "today",
            "week",
            "month"
          ]
        }
      },
      "TakingsFrom": {
        "name": "from",
        "in": "query",
        "description": "The first local date (YYYY-MM-DD); today when omitted.",
        "schema": {
          "type": "string",
          "format": "date"
        }
      },
      "TakingsTo": {
        "name": "to",
        "in": "query",
        "description": "The last local date, at most 366 days after `from`; `from` when omitted.",
        "schema": {
          "type": "string",
          "format": "date"
        }
      },
      "ProviderId": {
        "name": "provider_id",
        "in": "path",
        "required": true,
        "description": "A team member's id, from GET …/providers (or the login's `your_provider`).",
        "schema": {
          "type": "integer"
        }
      },
      "SyncService": {
        "name": "service",
        "in": "path",
        "required": true,
        "schema": {
          "$ref": "#/components/schemas/SyncService"
        }
      }
    },
    "responses": {
      "Deleted": {
        "description": "Deleted.",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "required": [
                "deleted",
                "id"
              ],
              "properties": {
                "deleted": {
                  "const": true
                },
                "id": {
                  "type": "integer"
                }
              }
            }
          }
        }
      },
      "BadRequest": {
        "description": "A parameter is missing or unreadable (`missing_parameter`, `invalid_parameter`).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "The key is missing or wrong (`unauthorized`).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Forbidden": {
        "description": "The record exists, but this account's role can't do that (`forbidden`).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "NotFound": {
        "description": "No such record, or it isn't this account's (`not_found`).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "NoCalendar": {
        "description": "No such kard (`not_found`), or the kard has no booking calendar yet (`no_calendar`).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "ValidationFailed": {
        "description": "The record didn't save (`validation_failed`); `details` has the messages per field.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unprocessable": {
        "description": "Refused, with a message written for a person: for bookings `invalid_transition`, `invalid_duration`, `offering_unavailable`, `too_late_to_request`, `invalid_recipient`, `invalid_parameter` and the booking engine's other refusals; for clients `not_a_possible_duplicate`; for setup `has_bookings`, `last_provider`, `cannot_join`, `too_many_rows`, `unreadable` and `invalid_parameter`; elsewhere `no_subscription`, `nothing_to_publish`, `nothing_to_checkpoint`, `checkpoint_limit`, `invalid_logo`, `invalid_design`.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "SlotTaken": {
        "description": "The time isn't free (`slot_taken`; `details.alternatives` lists up to 3 nearby open times), or the booking changed meanwhile (`stale_booking`: read it again).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "RateLimited": {
        "description": "Too many booking or calendar writes (`rate_limited`); wait `Retry-After` seconds and send it again.",
        "headers": {
          "Retry-After": {
            "description": "Seconds to wait.",
            "schema": {
              "type": "integer"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "CalendarNotFound": {
        "description": "No such calendar, or this key's person has no role on it; or no such booking, client, person or offering in it (`not_found`). The answer is the same either way, so it doesn't reveal whether a calendar exists.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "CalendarForbidden": {
        "description": "This login's role on the calendar is below what the operation needs, or (staff) the booking is someone else's (`forbidden`). The message says what this login can do here and which admins to ask.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "StaleBooking": {
        "description": "The booking changed since the `lock_version` you sent (`stale_booking`): nothing was overwritten. Read it again and decide.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "CalendarBooking": {
        "description": "The booking as it is now.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/DeskBookingEnvelope"
            }
          }
        }
      },
      "Removed": {
        "description": "Done; `message` says what it means.",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "required": [
                "deleted",
                "message"
              ],
              "properties": {
                "deleted": {
                  "const": true
                },
                "message": {
                  "type": "string",
                  "examples": [
                    "Sara Khan is off the team."
                  ]
                }
              }
            }
          }
        }
      },
      "TeamProvider": {
        "description": "The person, as their page shows them, and what to tell them.",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "required": [
                "provider"
              ],
              "properties": {
                "provider": {
                  "$ref": "#/components/schemas/TeamProviderDetail"
                },
                "message": {
                  "type": "string",
                  "examples": [
                    "Sara Khan is on the team, working Mon–Sat 10:00–19:00 (the opening hours). Check their hours."
                  ]
                }
              }
            }
          }
        }
      },
      "SpecialDaySaved": {
        "description": "Saved (201 new, 200 replaced).",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "required": [
                "special_day",
                "message"
              ],
              "properties": {
                "special_day": {
                  "$ref": "#/components/schemas/SpecialDay"
                },
                "message": {
                  "type": "string",
                  "examples": [
                    "Saved Sun 20 Jan: day off."
                  ]
                }
              }
            }
          }
        }
      },
      "InvitationNow": {
        "description": "The invitation as it is now.",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "required": [
                "invitation",
                "message"
              ],
              "properties": {
                "invitation": {
                  "$ref": "#/components/schemas/StaffInvitation"
                },
                "message": {
                  "type": "string",
                  "examples": [
                    "Invitation sent to pooja@example.com. The link works until Wed 23 Jan."
                  ]
                }
              }
            }
          }
        }
      },
      "WebhookNow": {
        "description": "The endpoint as it is now.",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "required": [
                "webhook",
                "message"
              ],
              "properties": {
                "webhook": {
                  "$ref": "#/components/schemas/CalendarWebhook"
                },
                "message": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "ImportRunning": {
        "description": "Another price list is still being read (`import_running`; `details.menu_import_id`): wait for it, then start again.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "ImportFinished": {
        "description": "Already added, discarded or expired (`already_finished`).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "ReadingPaused": {
        "description": "The reading service is out for today (`reading_paused`); try again tomorrow, or add the offerings by hand.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "NotConnected": {
        "description": "Not connected to that service, or not adding bookings (`not_connected`): the message says where to connect.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              },
              "details": {
                "type": "object",
                "description": "Per-field messages for validation_failed; alternatives (open times) for slot_taken."
              }
            }
          }
        }
      },
      "Pagination": {
        "type": "object",
        "required": [
          "page",
          "per_page",
          "total_pages",
          "total_count"
        ],
        "properties": {
          "page": {
            "type": "integer"
          },
          "per_page": {
            "type": "integer"
          },
          "total_pages": {
            "type": "integer"
          },
          "total_count": {
            "type": "integer"
          }
        }
      },
      "NamedRecord": {
        "type": "object",
        "required": [
          "id",
          "name"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "name": {
            "type": "string"
          }
        }
      },
      "Account": {
        "type": "object",
        "required": [
          "email",
          "name",
          "member_since"
        ],
        "properties": {
          "email": {
            "type": "string",
            "format": "email"
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "country": {
            "type": [
              "string",
              "null"
            ]
          },
          "timezone": {
            "type": [
              "string",
              "null"
            ]
          },
          "member_since": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Subscription": {
        "description": "The paid plan; null on the free tier.",
        "oneOf": [
          {
            "type": "null"
          },
          {
            "type": "object",
            "required": [
              "plan",
              "kind",
              "status",
              "active"
            ],
            "properties": {
              "plan": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "kind": {
                "type": "string"
              },
              "status": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "active": {
                "type": "boolean"
              },
              "current_period_end": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "cancelled_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              }
            }
          }
        ]
      },
      "KardSummary": {
        "type": "object",
        "required": [
          "share_link",
          "name",
          "is_active",
          "public_url",
          "created_at"
        ],
        "properties": {
          "share_link": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "tagline": {
            "type": [
              "string",
              "null"
            ]
          },
          "is_active": {
            "type": "boolean"
          },
          "organization": {
            "type": [
              "string",
              "null"
            ],
            "description": "The organization's slug, for an organization's kard."
          },
          "public_url": {
            "type": "string",
            "format": "uri"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Kard": {
        "allOf": [
          {
            "$ref": "#/components/schemas/KardSummary"
          },
          {
            "$ref": "#/components/schemas/SocialHandles"
          },
          {
            "type": "object",
            "required": [
              "indexable",
              "settings",
              "custom_links",
              "urls",
              "custom_design",
              "counts",
              "updated_at"
            ],
            "properties": {
              "nickname": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Private; only the owner sees it."
              },
              "description": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The About section, as plain text."
              },
              "indexable": {
                "type": [
                  "boolean",
                  "null"
                ],
                "description": "Whether search engines may index the public page."
              },
              "layout_type": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "settings": {
                "type": "object",
                "properties": {
                  "allow_connect_popup": {
                    "type": [
                      "boolean",
                      "null"
                    ]
                  },
                  "allow_email_notifications": {
                    "type": [
                      "boolean",
                      "null"
                    ]
                  },
                  "allow_reviews": {
                    "type": [
                      "boolean",
                      "null"
                    ]
                  }
                }
              },
              "photo_url": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "uri"
              },
              "custom_links": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/CustomLink"
                }
              },
              "urls": {
                "type": "object",
                "required": [
                  "public",
                  "qr_png",
                  "vcard"
                ],
                "properties": {
                  "public": {
                    "type": "string",
                    "format": "uri"
                  },
                  "qr_png": {
                    "type": "string",
                    "format": "uri"
                  },
                  "vcard": {
                    "type": "string",
                    "format": "uri"
                  }
                }
              },
              "custom_design": {
                "type": "object",
                "required": [
                  "published"
                ],
                "properties": {
                  "published": {
                    "type": "boolean"
                  },
                  "published_at": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time"
                  }
                }
              },
              "counts": {
                "type": "object",
                "required": [
                  "contacts",
                  "events",
                  "documents",
                  "custom_links"
                ],
                "properties": {
                  "contacts": {
                    "type": "integer"
                  },
                  "events": {
                    "type": "integer"
                  },
                  "documents": {
                    "type": "integer"
                  },
                  "custom_links": {
                    "type": "integer"
                  }
                }
              },
              "updated_at": {
                "type": "string",
                "format": "date-time"
              }
            }
          }
        ]
      },
      "KardEnvelope": {
        "type": "object",
        "required": [
          "kard"
        ],
        "properties": {
          "kard": {
            "$ref": "#/components/schemas/Kard"
          }
        }
      },
      "SocialHandles": {
        "type": "object",
        "properties": {
          "phone_number": {
            "type": [
              "string",
              "null"
            ]
          },
          "whatsapp_number": {
            "type": [
              "string",
              "null"
            ]
          },
          "email": {
            "type": [
              "string",
              "null"
            ]
          },
          "instagram_handle": {
            "type": [
              "string",
              "null"
            ]
          },
          "google_business_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "linkedin_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "upi_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "website": {
            "type": [
              "string",
              "null"
            ]
          },
          "telegram_handle": {
            "type": [
              "string",
              "null"
            ]
          },
          "youtube_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "facebook_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "twitter_handle": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "KardInput": {
        "allOf": [
          {
            "$ref": "#/components/schemas/SocialHandles"
          },
          {
            "type": "object",
            "properties": {
              "name": {
                "type": "string",
                "minLength": 4,
                "maxLength": 100
              },
              "tagline": {
                "type": "string"
              },
              "nickname": {
                "type": "string"
              },
              "description": {
                "type": "string",
                "description": "Plain text or simple HTML."
              },
              "is_active": {
                "type": "boolean",
                "description": "Inactive kards show an \"inactive\" public page."
              },
              "indexable": {
                "type": "boolean",
                "description": "Allow search engines to index the public page."
              },
              "layout_type": {
                "type": "string",
                "enum": [
                  "default",
                  "links",
                  "restaurant",
                  "letterhead",
                  "jharokha",
                  "mohur",
                  "yantra"
                ]
              },
              "allow_connect_popup_setting": {
                "type": "boolean"
              },
              "allow_email_notifications_setting": {
                "type": "boolean"
              },
              "allow_reviews_setting": {
                "type": "boolean"
              },
              "organization_id": {
                "type": "integer",
                "description": "Only for an organization you administer."
              },
              "custom_links_attributes": {
                "type": "array",
                "maxItems": 20,
                "items": {
                  "type": "object",
                  "properties": {
                    "id": {
                      "type": "integer",
                      "description": "Edits or removes that link; without it, a link is added."
                    },
                    "title": {
                      "type": "string"
                    },
                    "url": {
                      "type": "string",
                      "format": "uri"
                    },
                    "_destroy": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          }
        ]
      },
      "KardMultipart": {
        "type": "object",
        "description": "The same fields as JSON, named kard[field], plus the photo (JPEG, PNG, GIF or WebP, up to 5 MB) or kard[remove_photo]=1.",
        "properties": {
          "kard[name]": {
            "type": "string"
          },
          "kard[photo]": {
            "type": "string",
            "contentMediaType": "image/jpeg"
          },
          "kard[remove_photo]": {
            "type": "string",
            "enum": [
              "1"
            ]
          }
        }
      },
      "CustomLink": {
        "type": "object",
        "required": [
          "id",
          "title",
          "url"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "url": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "AccountAnalytics": {
        "type": "object",
        "required": [
          "kards",
          "totals"
        ],
        "properties": {
          "kards": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "kard",
                "name",
                "visits",
                "contacts"
              ],
              "properties": {
                "kard": {
                  "type": "string",
                  "description": "The kard's share_link."
                },
                "name": {
                  "type": "string"
                },
                "visits": {
                  "type": "object",
                  "required": [
                    "last_7_days",
                    "last_30_days",
                    "lifetime"
                  ],
                  "properties": {
                    "last_7_days": {
                      "type": "integer"
                    },
                    "last_30_days": {
                      "type": "integer"
                    },
                    "lifetime": {
                      "type": "integer"
                    }
                  }
                },
                "contacts": {
                  "type": "integer"
                }
              }
            }
          },
          "totals": {
            "type": "object",
            "required": [
              "kards",
              "visits_last_30_days",
              "visits_lifetime",
              "contacts"
            ],
            "properties": {
              "kards": {
                "type": "integer"
              },
              "visits_last_30_days": {
                "type": "integer"
              },
              "visits_lifetime": {
                "type": "integer"
              },
              "contacts": {
                "type": "integer"
              }
            }
          }
        }
      },
      "KardAnalytics": {
        "type": "object",
        "required": [
          "time_range",
          "totals",
          "visits_by_day",
          "device_types",
          "browsers",
          "countries",
          "regions",
          "cities",
          "referrers",
          "custom_link_clicks"
        ],
        "properties": {
          "time_range": {
            "type": "string",
            "enum": [
              "7_days",
              "30_days",
              "lifetime"
            ]
          },
          "totals": {
            "type": "object",
            "required": [
              "visits",
              "visits_lifetime",
              "contacts",
              "contacts_in_range"
            ],
            "properties": {
              "visits": {
                "type": "integer"
              },
              "visits_lifetime": {
                "type": "integer"
              },
              "contacts": {
                "type": "integer"
              },
              "contacts_in_range": {
                "type": "integer"
              }
            }
          },
          "visits_by_day": {
            "$ref": "#/components/schemas/Counts"
          },
          "device_types": {
            "$ref": "#/components/schemas/Counts"
          },
          "browsers": {
            "$ref": "#/components/schemas/Counts"
          },
          "countries": {
            "$ref": "#/components/schemas/Counts"
          },
          "regions": {
            "$ref": "#/components/schemas/Counts"
          },
          "cities": {
            "$ref": "#/components/schemas/Counts"
          },
          "referrers": {
            "$ref": "#/components/schemas/Counts"
          },
          "custom_link_clicks": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "id",
                "title",
                "url",
                "clicks"
              ],
              "properties": {
                "id": {
                  "type": "integer"
                },
                "title": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "url": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "clicks": {
                  "type": "integer"
                }
              }
            }
          }
        }
      },
      "Counts": {
        "type": "object",
        "description": "Label (or date) to count.",
        "additionalProperties": {
          "type": "integer"
        }
      },
      "Contact": {
        "type": "object",
        "required": [
          "id",
          "name",
          "custom_fields",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "kard": {
            "type": [
              "string",
              "null"
            ],
            "description": "The kard's share_link."
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "email": {
            "type": [
              "string",
              "null"
            ]
          },
          "phone_number": {
            "type": [
              "string",
              "null"
            ]
          },
          "message": {
            "type": [
              "string",
              "null"
            ]
          },
          "custom_fields": {
            "type": "object"
          },
          "event_id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ContactInput": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "country_code": {
            "type": "string",
            "examples": [
              "+91"
            ]
          },
          "phone_number": {
            "type": "string"
          },
          "message": {
            "type": "string"
          },
          "event_id": {
            "type": "integer"
          },
          "custom_fields": {
            "type": "object",
            "description": "The kard's own form fields, by name."
          }
        }
      },
      "ContactEnvelope": {
        "type": "object",
        "required": [
          "contact"
        ],
        "properties": {
          "contact": {
            "$ref": "#/components/schemas/Contact"
          }
        }
      },
      "ContactPage": {
        "type": "object",
        "required": [
          "contacts",
          "pagination"
        ],
        "properties": {
          "contacts": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Contact"
            }
          },
          "pagination": {
            "$ref": "#/components/schemas/Pagination"
          }
        }
      },
      "Event": {
        "type": "object",
        "required": [
          "id",
          "name",
          "archived",
          "contacts_count",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "note": {
            "type": [
              "string",
              "null"
            ]
          },
          "archived": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "contacts_count": {
            "type": "integer"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "EventInput": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "note": {
            "type": "string"
          },
          "archived": {
            "type": "boolean"
          }
        }
      },
      "EventEnvelope": {
        "type": "object",
        "required": [
          "event"
        ],
        "properties": {
          "event": {
            "$ref": "#/components/schemas/Event"
          }
        }
      },
      "Document": {
        "type": "object",
        "required": [
          "id",
          "slug",
          "public_url",
          "landing_page_published",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "slug": {
            "type": "string"
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "display_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "filename": {
            "type": [
              "string",
              "null"
            ]
          },
          "content_type": {
            "type": [
              "string",
              "null"
            ]
          },
          "byte_size": {
            "type": [
              "integer",
              "null"
            ]
          },
          "public_url": {
            "type": "string",
            "format": "uri"
          },
          "landing_page_published": {
            "type": "boolean"
          },
          "position": {
            "type": [
              "integer",
              "null"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "DocumentEnvelope": {
        "type": "object",
        "required": [
          "document"
        ],
        "properties": {
          "document": {
            "$ref": "#/components/schemas/Document"
          }
        }
      },
      "Design": {
        "type": "object",
        "required": [
          "published"
        ],
        "properties": {
          "draft_html": {
            "type": [
              "string",
              "null"
            ]
          },
          "published": {
            "type": "boolean"
          },
          "published_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "draft_saved_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "DesignEnvelope": {
        "type": "object",
        "required": [
          "design"
        ],
        "properties": {
          "design": {
            "$ref": "#/components/schemas/Design"
          },
          "sanitizer_removals": {
            "type": "array",
            "description": "On publish, what the sanitizer stripped for safety.",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "DesignInput": {
        "type": "object",
        "required": [
          "design"
        ],
        "properties": {
          "design": {
            "type": "object",
            "required": [
              "draft_html"
            ],
            "properties": {
              "draft_html": {
                "type": "string",
                "maxLength": 150000
              }
            }
          },
          "publish": {
            "type": "boolean",
            "description": "Publish the saved draft in the same call."
          }
        }
      },
      "DesignVersion": {
        "type": "object",
        "required": [
          "id",
          "source",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "source": {
            "type": "string",
            "enum": [
              "draft_save",
              "publish",
              "checkpoint",
              "restore",
              "ai_edit"
            ]
          },
          "label": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Qr": {
        "type": "object",
        "required": [
          "id",
          "properties",
          "logo_attached",
          "public_png_url"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "design_type": {
            "type": [
              "string",
              "null"
            ]
          },
          "properties": {
            "type": "object",
            "description": "The design keys set so far."
          },
          "logo_attached": {
            "type": "boolean"
          },
          "image_url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri"
          },
          "public_png_url": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "QrEnvelope": {
        "type": "object",
        "required": [
          "qr"
        ],
        "properties": {
          "qr": {
            "$ref": "#/components/schemas/Qr"
          }
        }
      },
      "QrDesign": {
        "type": "object",
        "description": "Strings; booleans as \"1\"/\"0\"; colours as #RRGGBB. Only send the keys you change.",
        "properties": {
          "module_px_size": {
            "type": "string",
            "description": "4 to 20"
          },
          "qr_shape": {
            "type": "string",
            "enum": [
              "square",
              "circle",
              "rounded_square"
            ]
          },
          "quiet_zone": {
            "type": "string",
            "description": "0 to 8"
          },
          "error_correction": {
            "type": "string",
            "enum": [
              "l",
              "m",
              "q",
              "h"
            ]
          },
          "background_color": {
            "type": "string"
          },
          "transparent_background": {
            "type": "string",
            "enum": [
              "0",
              "1"
            ]
          },
          "foreground_color": {
            "type": "string"
          },
          "canvas_color": {
            "type": "string"
          },
          "canvas_color_transparent": {
            "type": "string",
            "enum": [
              "0",
              "1"
            ]
          },
          "qr_corner_radius": {
            "type": "string",
            "description": "0 to 64"
          },
          "show_border": {
            "type": "string",
            "enum": [
              "0",
              "1"
            ]
          },
          "border_color": {
            "type": "string"
          },
          "border_width": {
            "type": "string",
            "description": "0 to 12"
          },
          "finder_pattern_style": {
            "type": "string",
            "enum": [
              "square",
              "circle",
              "rounded"
            ]
          },
          "finder_pattern_color": {
            "type": "string"
          },
          "dot_style": {
            "type": "string",
            "enum": [
              "classic",
              "circle",
              "square",
              "rounded_square",
              "rounded",
              "triangle",
              "diamond",
              "star",
              "hexagon",
              "cube"
            ]
          },
          "dot_size": {
            "type": "string",
            "description": "1 to 12"
          },
          "dot_corner_radius": {
            "type": "string",
            "description": "0 to 12"
          },
          "center_space_enabled": {
            "type": "string",
            "enum": [
              "0",
              "1"
            ]
          },
          "center_space_size": {
            "type": "string",
            "description": "4 to 14"
          },
          "show_center_text": {
            "type": "string",
            "enum": [
              "0",
              "1"
            ]
          },
          "center_text": {
            "type": "string",
            "maxLength": 30
          },
          "center_text_color": {
            "type": "string"
          },
          "center_text_font_size": {
            "type": "string",
            "description": "8 to 48"
          },
          "center_text_line_spacing": {
            "type": "string",
            "description": "0 to 60"
          },
          "center_text_rotation": {
            "type": "string",
            "description": "-180 to 180"
          },
          "show_logo": {
            "type": "string",
            "enum": [
              "0",
              "1"
            ]
          },
          "use_default_logo": {
            "type": "string",
            "enum": [
              "0",
              "1"
            ]
          },
          "logo_size": {
            "type": "string",
            "description": "4 to 14"
          },
          "show_footer": {
            "type": "string",
            "enum": [
              "0",
              "1"
            ]
          },
          "footer_text": {
            "type": "string",
            "maxLength": 60
          },
          "footer_color": {
            "type": "string"
          },
          "footer_font_size": {
            "type": "string",
            "description": "8 to 48"
          },
          "footer_position": {
            "type": "string"
          }
        }
      },
      "Organization": {
        "type": "object",
        "required": [
          "slug",
          "name",
          "role",
          "settings",
          "kards_count",
          "created_at"
        ],
        "properties": {
          "slug": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "layout_type": {
            "type": [
              "string",
              "null"
            ]
          },
          "role": {
            "type": "string",
            "description": "This account's role in it.",
            "enum": [
              "admin",
              "member",
              "none"
            ]
          },
          "settings": {
            "type": "object",
            "properties": {
              "show_logo_on_kards": {
                "type": [
                  "boolean",
                  "null"
                ]
              },
              "allow_dynamic_layouts": {
                "type": [
                  "boolean",
                  "null"
                ]
              }
            }
          },
          "kards_count": {
            "type": "integer"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "OrganizationInput": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "slug": {
            "type": "string"
          },
          "layout_type": {
            "type": "string",
            "enum": [
              "default",
              "links",
              "restaurant",
              "letterhead",
              "jharokha",
              "mohur",
              "yantra"
            ]
          },
          "show_logo_on_kards_setting": {
            "type": "boolean"
          },
          "allow_dynamic_layouts_setting": {
            "type": "boolean"
          }
        }
      },
      "OrganizationEnvelope": {
        "type": "object",
        "required": [
          "organization"
        ],
        "properties": {
          "organization": {
            "$ref": "#/components/schemas/Organization"
          }
        }
      },
      "Bookmark": {
        "type": "object",
        "required": [
          "id",
          "kard",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "kard": {
            "$ref": "#/components/schemas/KardSummary"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "SchedulingCalendar": {
        "type": "object",
        "required": [
          "id",
          "name",
          "preset",
          "time_zone",
          "currency"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "preset": {
            "$ref": "#/components/schemas/CalendarPreset"
          },
          "time_zone": {
            "type": "string",
            "description": "An IANA zone, such as Asia/Kolkata."
          },
          "currency": {
            "type": "string",
            "description": "ISO 4217."
          }
        }
      },
      "KardBooking": {
        "type": "object",
        "required": [
          "share_link",
          "accepts_bookings"
        ],
        "properties": {
          "share_link": {
            "type": "string"
          },
          "accepts_bookings": {
            "type": "boolean",
            "description": "Whether the kard shows a Book button now."
          },
          "booking_url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri"
          },
          "book_label": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "Book appointment",
              "Book with Priya"
            ]
          },
          "provider": {
            "description": "The person a team kard books.",
            "oneOf": [
              {
                "$ref": "#/components/schemas/NamedRecord"
              },
              {
                "type": "null"
              }
            ]
          }
        }
      },
      "Offering": {
        "type": "object",
        "required": [
          "id",
          "name",
          "slug",
          "duration_minutes",
          "duration_options",
          "price_mode",
          "visibility",
          "requires_approval",
          "providers"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "slug": {
            "type": "string"
          },
          "category": {
            "type": [
              "string",
              "null"
            ]
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "duration_minutes": {
            "type": "integer"
          },
          "duration_options": {
            "type": "array",
            "items": {
              "type": "integer"
            }
          },
          "price_cents": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Minor units of the calendar's currency."
          },
          "price_mode": {
            "type": "string",
            "enum": [
              "fixed",
              "from",
              "free",
              "hidden"
            ]
          },
          "price": {
            "type": [
              "string",
              "null"
            ],
            "description": "The price in words, such as \"₹800\", \"From ₹3,000\" or \"Free\"."
          },
          "location_kind": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "at_business",
              "at_client",
              "phone",
              "video",
              "custom",
              null
            ]
          },
          "visibility": {
            "type": "string",
            "description": "`unlisted`: link only; `internal`: front desk only.",
            "enum": [
              "public",
              "unlisted",
              "internal"
            ]
          },
          "requires_approval": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "A request the business approves, rather than an instant booking."
          },
          "providers": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "id",
                "name",
                "bookable_online"
              ],
              "properties": {
                "id": {
                  "type": "integer"
                },
                "name": {
                  "type": "string"
                },
                "bookable_online": {
                  "type": [
                    "boolean",
                    "null"
                  ]
                }
              }
            }
          },
          "booking_url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "description": "A deep link visitors can book, when the kard takes bookings."
          }
        }
      },
      "Slot": {
        "type": "object",
        "required": [
          "starts_at",
          "ends_at",
          "local_starts_at",
          "provider_ids"
        ],
        "properties": {
          "starts_at": {
            "type": "string",
            "format": "date-time"
          },
          "ends_at": {
            "type": "string",
            "format": "date-time"
          },
          "local_starts_at": {
            "type": "string",
            "format": "date-time",
            "description": "The start in the calendar's zone."
          },
          "provider_ids": {
            "type": "array",
            "description": "Who is free for the whole visit, in assignment order.",
            "items": {
              "type": "integer"
            }
          }
        }
      },
      "Booking": {
        "type": "object",
        "required": [
          "id",
          "status",
          "source",
          "title",
          "starts_at",
          "ends_at",
          "local_starts_at",
          "time_zone",
          "duration_minutes",
          "currency",
          "client",
          "items",
          "answers",
          "guest_emails",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "status": {
            "type": "string",
            "enum": [
              "requested",
              "booked",
              "arrived",
              "completed",
              "cancelled",
              "no_show",
              "declined"
            ]
          },
          "source": {
            "type": "string",
            "enum": [
              "online",
              "front_desk",
              "walk_in",
              "api"
            ]
          },
          "title": {
            "type": "string",
            "examples": [
              "Haircut + Blow-dry"
            ]
          },
          "starts_at": {
            "type": "string",
            "format": "date-time"
          },
          "ends_at": {
            "type": "string",
            "format": "date-time"
          },
          "local_starts_at": {
            "type": "string",
            "format": "date-time"
          },
          "time_zone": {
            "type": "string"
          },
          "duration_minutes": {
            "type": "integer"
          },
          "price_cents": {
            "type": [
              "integer",
              "null"
            ]
          },
          "currency": {
            "type": "string"
          },
          "price": {
            "type": [
              "string",
              "null"
            ],
            "description": "\"₹1,300\", \"Free\", or null when no price was set."
          },
          "client": {
            "type": "object",
            "required": [
              "id",
              "name"
            ],
            "properties": {
              "id": {
                "type": "integer"
              },
              "name": {
                "type": "string"
              },
              "phone": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "email": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          "client_time_zone": {
            "type": [
              "string",
              "null"
            ]
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BookingItem"
            }
          },
          "kard": {
            "$ref": "#/components/schemas/BookingKard"
          },
          "notes": {
            "type": [
              "string",
              "null"
            ]
          },
          "client_address": {
            "type": [
              "string",
              "null"
            ]
          },
          "answers": {
            "type": "object",
            "description": "Answers to the offering's questions, by question key."
          },
          "guest_emails": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "cancelled_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "cancelled_by": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "client",
              "business",
              null
            ]
          },
          "cancellation_reason": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "calendar": {
            "$ref": "#/components/schemas/SchedulingCalendar"
          }
        }
      },
      "BookingItem": {
        "type": "object",
        "required": [
          "offering",
          "provider",
          "starts_at",
          "ends_at",
          "duration_minutes"
        ],
        "properties": {
          "offering": {
            "type": "object",
            "required": [
              "id",
              "name",
              "slug"
            ],
            "properties": {
              "id": {
                "type": "integer"
              },
              "name": {
                "type": "string"
              },
              "slug": {
                "type": "string"
              }
            }
          },
          "provider": {
            "$ref": "#/components/schemas/NamedRecord"
          },
          "starts_at": {
            "type": "string",
            "format": "date-time"
          },
          "ends_at": {
            "type": "string",
            "format": "date-time"
          },
          "duration_minutes": {
            "type": "integer"
          },
          "price_cents": {
            "type": [
              "integer",
              "null"
            ]
          }
        }
      },
      "BookingEnvelope": {
        "type": "object",
        "required": [
          "booking"
        ],
        "properties": {
          "booking": {
            "$ref": "#/components/schemas/Booking"
          }
        }
      },
      "BookingPage": {
        "type": "object",
        "required": [
          "bookings",
          "pagination"
        ],
        "properties": {
          "bookings": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Booking"
            }
          },
          "pagination": {
            "$ref": "#/components/schemas/Pagination"
          }
        }
      },
      "BookingInput": {
        "type": "object",
        "required": [
          "offering_ids",
          "starts_at"
        ],
        "properties": {
          "offering_ids": {
            "type": "array",
            "description": "One or more offerings, back to back with one person.",
            "items": {
              "type": "integer"
            }
          },
          "starts_at": {
            "type": "string",
            "format": "date-time"
          },
          "client": {
            "type": "object",
            "description": "A new or matched client; front-desk bookings need only a name.",
            "properties": {
              "name": {
                "type": "string"
              },
              "phone": {
                "type": "string"
              },
              "email": {
                "type": "string",
                "format": "email"
              }
            }
          },
          "client_id": {
            "type": "integer",
            "description": "An existing client, instead of `client`."
          },
          "provider_id": {
            "type": "integer",
            "description": "Who with; anyone free (or the kard's linked person) when omitted."
          },
          "duration": {
            "type": "integer",
            "description": "One of the offering's duration options, in minutes."
          },
          "notes": {
            "type": "string"
          },
          "answers": {
            "type": "object"
          },
          "guest_emails": {
            "type": "array",
            "maxItems": 2,
            "items": {
              "type": "string",
              "format": "email"
            }
          },
          "channel": {
            "type": "string",
            "enum": [
              "phone",
              "whatsapp",
              "instagram",
              "in_person",
              "other"
            ]
          },
          "client_address": {
            "type": "string",
            "maxLength": 500,
            "description": "Where to go, for a service done at the client's address."
          },
          "overlap_allowed": {
            "type": "boolean",
            "description": "Book anyway over existing time; needs provider_id."
          },
          "idempotency_key": {
            "type": "string",
            "description": "A retry with the same key returns the first booking."
          }
        }
      },
      "BookingUpdate": {
        "type": "object",
        "properties": {
          "starts_at": {
            "type": "string",
            "format": "date-time"
          },
          "provider_id": {
            "type": "integer"
          },
          "overlap_allowed": {
            "type": "boolean"
          },
          "status": {
            "type": "string",
            "enum": [
              "arrived",
              "completed",
              "no_show",
              "booked",
              "declined",
              "cancelled"
            ]
          },
          "reason": {
            "type": "string",
            "description": "For declined and cancelled."
          }
        }
      },
      "BookingWebhookEvent": {
        "type": "object",
        "required": [
          "type",
          "timestamp",
          "data"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "booking.created",
              "booking.rescheduled",
              "booking.cancelled",
              "booking.status_changed"
            ]
          },
          "timestamp": {
            "type": "string",
            "format": "date-time"
          },
          "test": {
            "type": "boolean",
            "description": "True for the web's \"Send test\", with made-up client details."
          },
          "data": {
            "type": "object",
            "required": [
              "booking",
              "calendar"
            ],
            "properties": {
              "booking": {
                "$ref": "#/components/schemas/Booking"
              },
              "calendar": {
                "$ref": "#/components/schemas/SchedulingCalendar"
              },
              "source": {
                "type": "string",
                "description": "booking.created",
                "enum": [
                  "online",
                  "front_desk",
                  "walk_in",
                  "api"
                ]
              },
              "previous_starts_at": {
                "type": "string",
                "format": "date-time",
                "description": "booking.rescheduled"
              },
              "by": {
                "type": "string",
                "description": "booking.rescheduled and booking.cancelled",
                "enum": [
                  "client",
                  "business"
                ]
              },
              "from": {
                "type": "string",
                "description": "booking.status_changed"
              },
              "to": {
                "type": "string",
                "description": "booking.status_changed"
              },
              "reason": {
                "type": "string",
                "description": "\"expired\" when an unanswered request lapsed."
              }
            }
          }
        }
      },
      "BookingKard": {
        "description": "The kard it was booked from, if any.",
        "oneOf": [
          {
            "type": "null"
          },
          {
            "type": "object",
            "required": [
              "share_link",
              "public_url"
            ],
            "properties": {
              "share_link": {
                "type": "string"
              },
              "organization": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "public_url": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        ]
      },
      "CalendarPreset": {
        "type": "string",
        "description": "The kind of business, which sets the words its pages use.",
        "enum": [
          "consulting",
          "salon",
          "spa",
          "clinic",
          "tattoo",
          "home_services",
          "tutoring",
          "fitness",
          "other"
        ]
      },
      "CalendarRole": {
        "type": "string",
        "description": "`admin`: everything. `front_desk`: every booking (book, move, cancel, statuses, requests), walk-ins, clients, marking visits paid and advances received; not takings, the menu, settings or the team. `staff`: the schedule and every booking (read); statuses and the team note on their own visits, and their clients' details there only.",
        "enum": [
          "admin",
          "front_desk",
          "staff"
        ]
      },
      "CalendarLabels": {
        "type": "object",
        "description": "The words this kind of business uses (a salon's \"Service\", \"Client\" and \"Appointment\"; a consultant's \"Meeting type\", \"Invitee\" and \"Meeting\"). Use them when you talk to the owner.",
        "required": [
          "offering",
          "offerings",
          "provider",
          "providers",
          "client",
          "clients",
          "booking",
          "bookings",
          "public_provider",
          "public_providers"
        ],
        "properties": {
          "offering": {
            "type": "string",
            "examples": [
              "Service"
            ]
          },
          "offerings": {
            "type": "string",
            "examples": [
              "Services"
            ]
          },
          "provider": {
            "type": "string",
            "examples": [
              "Team member"
            ]
          },
          "providers": {
            "type": "string",
            "examples": [
              "Team members"
            ]
          },
          "client": {
            "type": "string",
            "examples": [
              "Client"
            ]
          },
          "clients": {
            "type": "string",
            "examples": [
              "Clients"
            ]
          },
          "booking": {
            "type": "string",
            "examples": [
              "Appointment"
            ]
          },
          "bookings": {
            "type": "string",
            "examples": [
              "Appointments"
            ]
          },
          "public_provider": {
            "type": "string",
            "description": "What clients call a provider.",
            "examples": [
              "stylist"
            ]
          },
          "public_providers": {
            "type": "string",
            "examples": [
              "stylists"
            ]
          }
        }
      },
      "CalendarSummary": {
        "type": "object",
        "required": [
          "id",
          "name",
          "role",
          "role_summary",
          "owner",
          "preset",
          "labels",
          "time_zone",
          "currency",
          "country",
          "today",
          "live",
          "your_provider",
          "web_url"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "role": {
            "$ref": "#/components/schemas/CalendarRole"
          },
          "role_summary": {
            "type": "string",
            "description": "What the role can do here, in words.",
            "examples": [
              "Everything: appointments, clients, money, services, settings and the team."
            ]
          },
          "owner": {
            "description": "A person's own calendar, or an organization's.",
            "oneOf": [
              {
                "type": "object",
                "required": [
                  "type",
                  "name"
                ],
                "properties": {
                  "type": {
                    "const": "user"
                  },
                  "name": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                }
              },
              {
                "type": "object",
                "required": [
                  "type",
                  "slug",
                  "name"
                ],
                "properties": {
                  "type": {
                    "const": "organization"
                  },
                  "slug": {
                    "type": "string"
                  },
                  "name": {
                    "type": "string"
                  }
                }
              }
            ]
          },
          "preset": {
            "$ref": "#/components/schemas/CalendarPreset"
          },
          "labels": {
            "$ref": "#/components/schemas/CalendarLabels"
          },
          "time_zone": {
            "type": "string",
            "description": "An IANA zone, such as Asia/Kolkata."
          },
          "currency": {
            "type": "string",
            "description": "ISO 4217."
          },
          "country": {
            "type": [
              "string",
              "null"
            ],
            "description": "ISO 3166-1 alpha-2."
          },
          "today": {
            "type": "string",
            "format": "date",
            "description": "The calendar's local date now."
          },
          "live": {
            "type": "object",
            "required": [
              "accepting_bookings",
              "bookable_online"
            ],
            "properties": {
              "accepting_bookings": {
                "type": "boolean",
                "description": "Online booking is switched on."
              },
              "bookable_online": {
                "type": "boolean",
                "description": "There's something clients can book online."
              }
            }
          },
          "your_provider": {
            "description": "On GET /calendars/{id}, the person this login is on the team, if any; always null in the list.",
            "oneOf": [
              {
                "$ref": "#/components/schemas/NamedRecord"
              },
              {
                "type": "null"
              }
            ]
          },
          "web_url": {
            "type": "string",
            "format": "uri",
            "description": "The calendar on the web."
          }
        }
      },
      "Calendar": {
        "allOf": [
          {
            "$ref": "#/components/schemas/CalendarSummary"
          },
          {
            "type": "object",
            "required": [
              "features",
              "providers",
              "offerings"
            ],
            "properties": {
              "features": {
                "type": "object",
                "description": "Which parts of Calendar the business uses.",
                "required": [
                  "team",
                  "front_desk",
                  "walk_ins",
                  "processing",
                  "buffers",
                  "intake_questions",
                  "request_to_book",
                  "advance"
                ],
                "properties": {
                  "team": {
                    "type": "boolean"
                  },
                  "front_desk": {
                    "type": "boolean"
                  },
                  "walk_ins": {
                    "type": "boolean"
                  },
                  "processing": {
                    "type": "boolean"
                  },
                  "buffers": {
                    "type": "boolean"
                  },
                  "intake_questions": {
                    "type": "boolean"
                  },
                  "request_to_book": {
                    "type": "boolean"
                  },
                  "advance": {
                    "type": "boolean"
                  }
                }
              },
              "providers": {
                "type": "array",
                "description": "The people who take bookings, inactive ones included.",
                "items": {
                  "$ref": "#/components/schemas/CalendarProvider"
                }
              },
              "offerings": {
                "type": [
                  "array",
                  "null"
                ],
                "description": "The front desk and admins: the menu the desk books from (active offerings, front-desk-only ones included). Null for staff.",
                "items": {
                  "$ref": "#/components/schemas/CalendarOffering"
                }
              }
            }
          }
        ]
      },
      "CalendarProvider": {
        "type": "object",
        "required": [
          "id",
          "name",
          "title",
          "color",
          "active",
          "bookable_online",
          "kard"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "title": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "Senior stylist"
            ]
          },
          "color": {
            "type": "string",
            "description": "Their colour on the schedule.",
            "enum": [
              "clay",
              "amber",
              "ochre",
              "olive",
              "sage",
              "teal",
              "sky",
              "indigo",
              "plum",
              "rose"
            ]
          },
          "active": {
            "type": "boolean",
            "description": "False once they stop taking bookings (their past visits stay)."
          },
          "bookable_online": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "kard": {
            "type": [
              "string",
              "null"
            ],
            "description": "The share_link of the kard that books them, if any."
          }
        }
      },
      "CalendarOffering": {
        "type": "object",
        "required": [
          "id",
          "name",
          "slug",
          "category",
          "duration_minutes",
          "duration_options",
          "price_cents",
          "price_mode",
          "price",
          "advance_cents",
          "location_kind",
          "visibility",
          "requires_approval",
          "provider_ids"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "slug": {
            "type": "string"
          },
          "category": {
            "type": [
              "string",
              "null"
            ]
          },
          "duration_minutes": {
            "type": "integer"
          },
          "duration_options": {
            "type": "array",
            "items": {
              "type": "integer"
            }
          },
          "price_cents": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Minor units of the calendar's currency."
          },
          "price_mode": {
            "type": "string",
            "enum": [
              "fixed",
              "from",
              "free",
              "hidden"
            ]
          },
          "price": {
            "type": [
              "string",
              "null"
            ],
            "description": "The price in words, such as \"₹800\", \"From ₹3,000\" or \"Free\"."
          },
          "advance_cents": {
            "type": [
              "integer",
              "null"
            ],
            "description": "The advance a client is asked for when booking, if any."
          },
          "location_kind": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "at_business",
              "at_client",
              "phone",
              "video",
              "custom",
              null
            ]
          },
          "visibility": {
            "type": "string",
            "description": "`unlisted`: link only; `internal`: front desk only.",
            "enum": [
              "public",
              "unlisted",
              "internal"
            ]
          },
          "requires_approval": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "A request the business approves, rather than an instant booking."
          },
          "provider_ids": {
            "type": "array",
            "description": "Who does it.",
            "items": {
              "type": "integer"
            }
          }
        }
      },
      "ScheduleDay": {
        "type": "object",
        "required": [
          "view",
          "date",
          "today",
          "time_zone",
          "closed",
          "summary",
          "free_now",
          "people"
        ],
        "properties": {
          "view": {
            "const": "day"
          },
          "date": {
            "type": "string",
            "format": "date"
          },
          "today": {
            "type": "boolean",
            "description": "Whether `date` is the calendar's today."
          },
          "time_zone": {
            "type": "string"
          },
          "closed": {
            "description": "A closure covering the whole day (\"Closed for Diwali\"), or null.",
            "oneOf": [
              {
                "type": "null"
              },
              {
                "type": "object",
                "required": [
                  "note",
                  "starts_at",
                  "ends_at"
                ],
                "properties": {
                  "note": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "starts_at": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "ends_at": {
                    "type": "string",
                    "format": "date-time"
                  }
                }
              }
            ]
          },
          "summary": {
            "type": "object",
            "required": [
              "booked",
              "requests",
              "unconfirmed",
              "on_leave",
              "off",
              "requests_waiting"
            ],
            "properties": {
              "booked": {
                "type": "integer",
                "description": "Visits that day."
              },
              "requests": {
                "type": "integer",
                "description": "Requests to approve that day."
              },
              "unconfirmed": {
                "type": "integer",
                "description": "Reminded, not confirmed yet: the likeliest no-shows."
              },
              "on_leave": {
                "type": "array",
                "description": "The names of people on leave.",
                "items": {
                  "type": "string"
                }
              },
              "off": {
                "type": "array",
                "description": "The names of people with the day off and nothing booked.",
                "items": {
                  "type": "string"
                }
              },
              "requests_waiting": {
                "type": "integer",
                "description": "Requests to approve, on any day."
              }
            }
          },
          "free_now": {
            "type": "array",
            "description": "Today: who is free right now, and until when (local).",
            "items": {
              "type": "object",
              "required": [
                "provider",
                "until"
              ],
              "properties": {
                "provider": {
                  "$ref": "#/components/schemas/NamedRecord"
                },
                "until": {
                  "type": "string",
                  "examples": [
                    "15:00"
                  ]
                }
              }
            }
          },
          "people": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "provider",
                "status",
                "note",
                "hours",
                "blocks",
                "bookings"
              ],
              "properties": {
                "provider": {
                  "$ref": "#/components/schemas/CalendarProvider"
                },
                "status": {
                  "type": "string",
                  "description": "`day_off`: no hours that date; `leave`: time off covers every working hour; `inactive`: no longer taking bookings (shown for their visits).",
                  "enum": [
                    "working",
                    "day_off",
                    "leave",
                    "inactive"
                  ]
                },
                "note": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "examples": [
                    "On leave until Fri 11 Jan"
                  ]
                },
                "hours": {
                  "type": "array",
                  "description": "Local working hours, as [from, to] pairs.",
                  "items": {
                    "type": "array",
                    "minItems": 2,
                    "maxItems": 2,
                    "items": {
                      "type": "string"
                    }
                  },
                  "examples": [
                    [
                      [
                        "10:00",
                        "13:00"
                      ],
                      [
                        "14:00",
                        "19:00"
                      ]
                    ]
                  ]
                },
                "blocks": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/ScheduleBlock"
                  }
                },
                "bookings": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/ScheduleBooking"
                  }
                }
              }
            }
          }
        }
      },
      "ScheduleWeek": {
        "type": "object",
        "required": [
          "view",
          "date",
          "monday",
          "time_zone",
          "provider",
          "days"
        ],
        "properties": {
          "view": {
            "const": "week"
          },
          "date": {
            "type": "string",
            "format": "date"
          },
          "monday": {
            "type": "string",
            "format": "date"
          },
          "time_zone": {
            "type": "string"
          },
          "provider": {
            "description": "The one person shown, with `provider_id`.",
            "oneOf": [
              {
                "$ref": "#/components/schemas/NamedRecord"
              },
              {
                "type": "null"
              }
            ]
          },
          "days": {
            "type": "array",
            "description": "Monday to Sunday.",
            "items": {
              "type": "object",
              "required": [
                "date",
                "status",
                "note",
                "booked",
                "blocks",
                "bookings"
              ],
              "properties": {
                "date": {
                  "type": "string",
                  "format": "date"
                },
                "status": {
                  "type": "string",
                  "description": "`closed`: nobody works, or a closure.",
                  "enum": [
                    "open",
                    "closed"
                  ]
                },
                "note": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "Why a closed day is closed."
                },
                "booked": {
                  "type": "integer",
                  "description": "Visits that day."
                },
                "blocks": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/ScheduleBlock"
                  }
                },
                "bookings": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/ScheduleBooking"
                  }
                }
              }
            }
          }
        }
      },
      "ScheduleBlock": {
        "type": "object",
        "description": "A break, time off, blocked time or a closure on the grid.",
        "required": [
          "id",
          "kind",
          "label",
          "from",
          "to",
          "whole_calendar"
        ],
        "properties": {
          "id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Null for the gap between two shifts of a split day."
          },
          "kind": {
            "type": "string",
            "enum": [
              "break",
              "time_off",
              "blocked",
              "closed"
            ]
          },
          "label": {
            "type": "string",
            "examples": [
              "Product training"
            ]
          },
          "from": {
            "type": "string",
            "examples": [
              "17:00"
            ]
          },
          "to": {
            "type": "string",
            "examples": [
              "18:00"
            ]
          },
          "whole_calendar": {
            "type": "boolean",
            "description": "For everyone, not one person."
          }
        }
      },
      "ScheduleBooking": {
        "type": "object",
        "description": "A visit on the schedule. The client's other details are on GET /calendars/{calendar_id}/bookings/{id}.",
        "required": [
          "id",
          "status",
          "title",
          "starts_at",
          "ends_at",
          "local",
          "client",
          "source",
          "confirmed"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "status": {
            "type": "string",
            "enum": [
              "requested",
              "booked",
              "arrived",
              "completed",
              "cancelled",
              "no_show",
              "declined"
            ]
          },
          "title": {
            "type": "string"
          },
          "starts_at": {
            "type": "string",
            "format": "date-time"
          },
          "ends_at": {
            "type": "string",
            "format": "date-time"
          },
          "local": {
            "type": "string",
            "description": "The local times.",
            "examples": [
              "15:00–16:30"
            ]
          },
          "provider": {
            "$ref": "#/components/schemas/NamedRecord"
          },
          "client": {
            "type": "object",
            "required": [
              "id",
              "name",
              "alert_note"
            ],
            "properties": {
              "id": {
                "type": "integer"
              },
              "name": {
                "type": "string"
              },
              "alert_note": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Null where this login doesn't see the client's details (staff, on others' visits)."
              }
            }
          },
          "source": {
            "type": "string",
            "enum": [
              "online",
              "front_desk",
              "walk_in",
              "api"
            ]
          },
          "confirmed": {
            "type": "boolean",
            "description": "The client confirmed."
          },
          "overlap": {
            "type": "boolean",
            "description": "The day view: it overlaps another visit of the same person (booked anyway)."
          }
        }
      },
      "LockVersion": {
        "type": "integer",
        "description": "The booking's lock_version, from its JSON. If someone changed the booking since, nothing is overwritten (409 stale_booking)."
      },
      "DeskBooking": {
        "type": "object",
        "description": "The webhook's booking (Booking), plus what the desk's booking sheet shows. Staff read every visit, but on someone else's the client's `phone`, `email` and `alert_note`, and `notes`, `answers`, `guest_emails`, `client_address`, `internal_notes` and `history` are null, as on the web; `payment` and `advance` are null for staff.\n",
        "required": [
          "id",
          "status",
          "source",
          "title",
          "starts_at",
          "ends_at",
          "local_starts_at",
          "time_zone",
          "duration_minutes",
          "price_cents",
          "currency",
          "price",
          "client",
          "client_time_zone",
          "items",
          "kard",
          "notes",
          "client_address",
          "answers",
          "guest_emails",
          "cancelled_at",
          "cancelled_by",
          "cancellation_reason",
          "created_at",
          "updated_at",
          "lock_version",
          "channel",
          "created_by",
          "provider",
          "internal_notes",
          "video_url",
          "overlap_allowed",
          "confirmed_at",
          "reminder_sent_at",
          "request_expires_at",
          "payment",
          "advance",
          "can",
          "history"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "status": {
            "type": "string",
            "enum": [
              "requested",
              "booked",
              "arrived",
              "completed",
              "cancelled",
              "no_show",
              "declined"
            ]
          },
          "source": {
            "type": "string",
            "enum": [
              "online",
              "front_desk",
              "walk_in",
              "api"
            ]
          },
          "title": {
            "type": "string",
            "examples": [
              "Haircut + Blow-dry"
            ]
          },
          "starts_at": {
            "type": "string",
            "format": "date-time"
          },
          "ends_at": {
            "type": "string",
            "format": "date-time"
          },
          "local_starts_at": {
            "type": "string",
            "format": "date-time"
          },
          "time_zone": {
            "type": "string"
          },
          "duration_minutes": {
            "type": "integer"
          },
          "price_cents": {
            "type": [
              "integer",
              "null"
            ]
          },
          "currency": {
            "type": "string"
          },
          "price": {
            "type": [
              "string",
              "null"
            ],
            "description": "\"₹1,300\", \"Free\", or null when no price was set."
          },
          "client": {
            "type": "object",
            "required": [
              "id",
              "name",
              "phone",
              "email",
              "alert_note",
              "bookings_count",
              "no_show_count",
              "cancelled_count",
              "new"
            ],
            "properties": {
              "id": {
                "type": "integer"
              },
              "name": {
                "type": "string"
              },
              "phone": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "email": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "alert_note": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Shows on every booking of theirs (\"Nut allergy\")."
              },
              "bookings_count": {
                "type": "integer"
              },
              "no_show_count": {
                "type": "integer"
              },
              "cancelled_count": {
                "type": "integer"
              },
              "new": {
                "type": "boolean",
                "description": "A first-time client."
              }
            }
          },
          "client_time_zone": {
            "type": [
              "string",
              "null"
            ]
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BookingItem"
            }
          },
          "kard": {
            "$ref": "#/components/schemas/BookingKard"
          },
          "notes": {
            "type": [
              "string",
              "null"
            ],
            "description": "The client's note when booking."
          },
          "client_address": {
            "type": [
              "string",
              "null"
            ]
          },
          "answers": {
            "type": [
              "object",
              "null"
            ],
            "description": "Answers to the offering's questions, by question key."
          },
          "guest_emails": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "string"
            }
          },
          "cancelled_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "cancelled_by": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "client",
              "business",
              null
            ]
          },
          "cancellation_reason": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "lock_version": {
            "$ref": "#/components/schemas/LockVersion"
          },
          "channel": {
            "type": [
              "string",
              "null"
            ],
            "description": "How the desk took it.",
            "enum": [
              "phone",
              "whatsapp",
              "instagram",
              "in_person",
              "other",
              null
            ]
          },
          "created_by": {
            "description": "Who booked it at the desk or through the API.",
            "oneOf": [
              {
                "type": "null"
              },
              {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string"
                  }
                }
              }
            ]
          },
          "provider": {
            "description": "The person it's with.",
            "oneOf": [
              {
                "$ref": "#/components/schemas/NamedRecord"
              },
              {
                "type": "null"
              }
            ]
          },
          "internal_notes": {
            "type": [
              "string",
              "null"
            ],
            "description": "The team's note; clients never see it."
          },
          "video_url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri"
          },
          "overlap_allowed": {
            "type": "boolean",
            "description": "Booked anyway over someone's time."
          },
          "confirmed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the client confirmed."
          },
          "reminder_sent_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "request_expires_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When a request's hold runs out."
          },
          "payment": {
            "description": "The front desk and admins; null for staff.",
            "oneOf": [
              {
                "$ref": "#/components/schemas/BookingPayment"
              },
              {
                "type": "null"
              }
            ]
          },
          "advance": {
            "description": "The advance the client was asked for; null when none was (and for staff).",
            "oneOf": [
              {
                "$ref": "#/components/schemas/BookingAdvance"
              },
              {
                "type": "null"
              }
            ]
          },
          "can": {
            "type": "object",
            "required": [
              "work",
              "manage"
            ],
            "properties": {
              "work": {
                "type": "boolean",
                "description": "This login may change its status and team note, and sees the client's details."
              },
              "manage": {
                "type": "boolean",
                "description": "This login may also book, move, cancel, answer requests and handle the money."
              }
            }
          },
          "history": {
            "type": [
              "array",
              "null"
            ],
            "description": "One booking (GET …/bookings/{id}): the client's last 3 earlier visits. Null in lists, and where this login doesn't see the client's details.",
            "items": {
              "$ref": "#/components/schemas/VisitSummary"
            }
          }
        }
      },
      "BookingPayment": {
        "type": "object",
        "required": [
          "paid",
          "payable",
          "paid_cents",
          "tip_cents",
          "payment_method",
          "paid_at",
          "amount_due_cents"
        ],
        "properties": {
          "paid": {
            "type": "boolean"
          },
          "payable": {
            "type": "boolean",
            "description": "It can be marked paid (it has started), or already is."
          },
          "paid_cents": {
            "type": [
              "integer",
              "null"
            ]
          },
          "tip_cents": {
            "type": [
              "integer",
              "null"
            ]
          },
          "payment_method": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "cash",
              "upi",
              "card",
              "other",
              null
            ]
          },
          "paid_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "amount_due_cents": {
            "type": [
              "integer",
              "null"
            ],
            "description": "What \"Mark paid\" starts from, the price less an advance received."
          }
        }
      },
      "BookingAdvance": {
        "type": "object",
        "required": [
          "amount_cents",
          "received_at",
          "due",
          "outcome"
        ],
        "properties": {
          "amount_cents": {
            "type": "integer"
          },
          "received_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "due": {
            "type": "boolean",
            "description": "Asked for and not received yet."
          },
          "outcome": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "refunded",
              "kept",
              null
            ],
            "description": "A received advance on a visit that's off (cancelled or declined), once the desk says whether it was refunded or kept; null otherwise."
          }
        }
      },
      "DeskBookingEnvelope": {
        "type": "object",
        "required": [
          "booking"
        ],
        "properties": {
          "booking": {
            "$ref": "#/components/schemas/DeskBooking"
          }
        }
      },
      "DeskBookingPage": {
        "type": "object",
        "required": [
          "bookings",
          "pagination"
        ],
        "properties": {
          "bookings": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DeskBooking"
            }
          },
          "pagination": {
            "$ref": "#/components/schemas/Pagination"
          }
        }
      },
      "CalendarBookingInput": {
        "type": "object",
        "required": [
          "offering_ids"
        ],
        "properties": {
          "offering_ids": {
            "type": "array",
            "description": "One or more offerings, back to back with one person.",
            "items": {
              "type": "integer"
            }
          },
          "starts_at": {
            "type": "string",
            "format": "date-time",
            "description": "Required, except for a walk-in (which starts now)."
          },
          "source": {
            "type": "string",
            "description": "`walk_in`: someone in the shop now (where `features.walk_ins` is on).",
            "enum": [
              "api",
              "walk_in"
            ],
            "default": "api"
          },
          "client": {
            "type": "object",
            "description": "A new or matched client; the front desk needs only a name. A walk-in without a client files under the desk's anonymous \"Walk-in\".",
            "properties": {
              "name": {
                "type": "string"
              },
              "phone": {
                "type": "string"
              },
              "email": {
                "type": "string",
                "format": "email"
              }
            }
          },
          "client_id": {
            "type": "integer",
            "description": "An existing client, instead of `client`."
          },
          "provider_id": {
            "type": "integer",
            "description": "Who with; anyone free when omitted."
          },
          "duration": {
            "type": "integer",
            "description": "One of the offering's duration options, in minutes."
          },
          "notes": {
            "type": "string"
          },
          "internal_notes": {
            "type": "string",
            "description": "A note only the team sees."
          },
          "answers": {
            "type": "object"
          },
          "guest_emails": {
            "type": "array",
            "maxItems": 2,
            "items": {
              "type": "string",
              "format": "email"
            }
          },
          "channel": {
            "type": "string",
            "enum": [
              "phone",
              "whatsapp",
              "instagram",
              "in_person",
              "other"
            ]
          },
          "client_address": {
            "type": "string",
            "maxLength": 500,
            "description": "Where to go, for a service done at the client's address."
          },
          "overlap_allowed": {
            "type": "boolean",
            "description": "Book anyway over existing time; needs provider_id."
          },
          "idempotency_key": {
            "type": "string",
            "description": "A retry with the same key returns the first booking."
          }
        }
      },
      "VisitSummary": {
        "type": "object",
        "description": "A visit in a list (a client's history, upcoming and unpaid visits).",
        "required": [
          "id",
          "status",
          "title",
          "starts_at",
          "local_starts_at",
          "provider",
          "price_cents",
          "paid_cents"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "status": {
            "type": "string",
            "enum": [
              "requested",
              "booked",
              "arrived",
              "completed",
              "cancelled",
              "no_show",
              "declined"
            ]
          },
          "title": {
            "type": "string"
          },
          "starts_at": {
            "type": "string",
            "format": "date-time"
          },
          "local_starts_at": {
            "type": "string",
            "format": "date-time"
          },
          "provider": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/NamedRecord"
              },
              {
                "type": "null"
              }
            ]
          },
          "price_cents": {
            "type": [
              "integer",
              "null"
            ]
          },
          "paid_cents": {
            "type": [
              "integer",
              "null"
            ]
          }
        }
      },
      "ClientSummary": {
        "type": "object",
        "required": [
          "id",
          "name",
          "phone",
          "email",
          "alert_note",
          "blocked",
          "source",
          "visits",
          "bookings_count",
          "no_show_count",
          "cancelled_count",
          "last_visit_at",
          "next_booking",
          "usual",
          "possible_duplicate_of",
          "whatsapp_opted_in",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "phone": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "+919876543210"
            ]
          },
          "email": {
            "type": [
              "string",
              "null"
            ]
          },
          "alert_note": {
            "type": [
              "string",
              "null"
            ],
            "description": "Shows on every booking of theirs (\"Nut allergy\")."
          },
          "blocked": {
            "type": "boolean",
            "description": "They can't book online (the desk still can)."
          },
          "source": {
            "type": [
              "string",
              "null"
            ],
            "description": "How they were added.",
            "enum": [
              "online",
              "front_desk",
              "import",
              "contacts",
              null
            ]
          },
          "visits": {
            "type": "integer",
            "description": "Times they turned up (arrived or completed)."
          },
          "bookings_count": {
            "type": "integer"
          },
          "no_show_count": {
            "type": "integer"
          },
          "cancelled_count": {
            "type": "integer"
          },
          "last_visit_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "next_booking": {
            "description": "Their current or next booking.",
            "oneOf": [
              {
                "$ref": "#/components/schemas/VisitSummary"
              },
              {
                "type": "null"
              }
            ]
          },
          "usual": {
            "description": "What they book most, and with whom.",
            "oneOf": [
              {
                "type": "null"
              },
              {
                "type": "object",
                "required": [
                  "offering",
                  "provider",
                  "times"
                ],
                "properties": {
                  "offering": {
                    "type": "string",
                    "examples": [
                      "Root touch-up"
                    ]
                  },
                  "provider": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "examples": [
                      "Priya"
                    ]
                  },
                  "times": {
                    "type": "integer"
                  }
                }
              }
            ]
          },
          "possible_duplicate_of": {
            "type": [
              "integer",
              "null"
            ],
            "description": "The client this may be the same person as (merge them, or unlink them)."
          },
          "whatsapp_opted_in": {
            "type": "boolean"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Client": {
        "type": "object",
        "description": "One client, as the client's page shows them.",
        "required": [
          "id",
          "name",
          "phone",
          "email",
          "alert_note",
          "blocked",
          "source",
          "visits",
          "bookings_count",
          "no_show_count",
          "cancelled_count",
          "last_visit_at",
          "possible_duplicate_of",
          "whatsapp_opted_in",
          "created_at",
          "notes",
          "paid_cents",
          "currency",
          "first_booked_at",
          "last_visit",
          "upcoming",
          "history",
          "history_count",
          "possible_duplicates"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "phone": {
            "type": [
              "string",
              "null"
            ]
          },
          "email": {
            "type": [
              "string",
              "null"
            ]
          },
          "alert_note": {
            "type": [
              "string",
              "null"
            ],
            "description": "Shows on every booking of theirs (\"Nut allergy\")."
          },
          "blocked": {
            "type": "boolean",
            "description": "They can't book online (the desk still can)."
          },
          "source": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "online",
              "front_desk",
              "import",
              "contacts",
              null
            ]
          },
          "visits": {
            "type": "integer",
            "description": "Times they turned up (arrived or completed)."
          },
          "bookings_count": {
            "type": "integer"
          },
          "no_show_count": {
            "type": "integer"
          },
          "cancelled_count": {
            "type": "integer"
          },
          "last_visit_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "next_booking": {
            "description": "Only in the list (null here); see `upcoming`.",
            "type": "null"
          },
          "usual": {
            "description": "Only in the list (null here).",
            "type": "null"
          },
          "possible_duplicate_of": {
            "description": "The client this may be the same person as.",
            "oneOf": [
              {
                "$ref": "#/components/schemas/PossibleDuplicate"
              },
              {
                "type": "null"
              }
            ]
          },
          "whatsapp_opted_in": {
            "type": "boolean"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "notes": {
            "type": [
              "string",
              "null"
            ],
            "description": "The team's notes on the client; they can hold health details."
          },
          "paid_cents": {
            "type": "integer",
            "description": "Everything marked paid, in the calendar's currency."
          },
          "currency": {
            "type": "string"
          },
          "first_booked_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "last_visit": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/VisitSummary"
              },
              {
                "type": "null"
              }
            ]
          },
          "upcoming": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/VisitSummary"
            }
          },
          "history": {
            "type": "array",
            "description": "Earlier visits, the latest 10 (all with `history=all`).",
            "items": {
              "$ref": "#/components/schemas/VisitSummary"
            }
          },
          "history_count": {
            "type": "integer"
          },
          "possible_duplicates": {
            "type": "array",
            "description": "Newer records that may be this same person.",
            "items": {
              "$ref": "#/components/schemas/PossibleDuplicate"
            }
          }
        }
      },
      "PossibleDuplicate": {
        "type": "object",
        "required": [
          "id",
          "name",
          "phone",
          "email"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "phone": {
            "type": [
              "string",
              "null"
            ]
          },
          "email": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "ClientEnvelope": {
        "type": "object",
        "required": [
          "client"
        ],
        "properties": {
          "client": {
            "$ref": "#/components/schemas/Client"
          }
        }
      },
      "ClientInput": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "phone": {
            "type": "string"
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "notes": {
            "type": "string"
          },
          "alert_note": {
            "type": "string",
            "maxLength": 200,
            "description": "Shows on every booking of theirs."
          },
          "blocked": {
            "type": "boolean",
            "description": "Stops them booking online (the desk still can)."
          }
        }
      },
      "Takings": {
        "type": "object",
        "required": [
          "from",
          "to",
          "days",
          "currency",
          "taken_cents",
          "taken",
          "tips_cents",
          "visits",
          "by_day",
          "by_provider",
          "by_method",
          "other_currency_visits",
          "unpaid"
        ],
        "properties": {
          "from": {
            "type": "string",
            "format": "date"
          },
          "to": {
            "type": "string",
            "format": "date"
          },
          "days": {
            "type": "integer"
          },
          "currency": {
            "type": "string"
          },
          "taken_cents": {
            "type": "integer",
            "description": "Everything marked paid, in the calendar's currency."
          },
          "taken": {
            "type": "string",
            "examples": [
              "₹12,500"
            ]
          },
          "tips_cents": {
            "type": "integer"
          },
          "visits": {
            "type": "integer",
            "description": "Paid visits."
          },
          "by_day": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "date",
                "taken_cents",
                "tips_cents",
                "visits"
              ],
              "properties": {
                "date": {
                  "type": "string",
                  "format": "date"
                },
                "taken_cents": {
                  "type": "integer"
                },
                "tips_cents": {
                  "type": "integer"
                },
                "visits": {
                  "type": "integer"
                }
              }
            }
          },
          "by_provider": {
            "type": "array",
            "description": "Most taken first; `key` is the person's id.",
            "items": {
              "$ref": "#/components/schemas/TakingsShare"
            }
          },
          "by_method": {
            "type": "array",
            "description": "Most taken first; `key` is cash, upi, card, other or advance.",
            "items": {
              "$ref": "#/components/schemas/TakingsShare"
            }
          },
          "other_currency_visits": {
            "type": "integer",
            "description": "Paid visits in another currency, left out of the totals."
          },
          "unpaid": {
            "type": "object",
            "description": "Finished visits nobody marked paid.",
            "required": [
              "count",
              "not_shown",
              "visits"
            ],
            "properties": {
              "count": {
                "type": "integer"
              },
              "not_shown": {
                "type": "integer",
                "description": "How many of them aren't in `visits` (it holds the 50 most recent)."
              },
              "visits": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/VisitWithClient"
                }
              }
            }
          }
        }
      },
      "TakingsShare": {
        "type": "object",
        "required": [
          "key",
          "label",
          "taken_cents",
          "tips_cents",
          "visits"
        ],
        "properties": {
          "key": {
            "type": [
              "integer",
              "string",
              "null"
            ]
          },
          "label": {
            "type": "string",
            "examples": [
              "Priya Sharma",
              "UPI"
            ]
          },
          "taken_cents": {
            "type": "integer"
          },
          "tips_cents": {
            "type": "integer"
          },
          "visits": {
            "type": "integer"
          }
        }
      },
      "VisitWithClient": {
        "allOf": [
          {
            "$ref": "#/components/schemas/VisitSummary"
          },
          {
            "type": "object",
            "required": [
              "client"
            ],
            "properties": {
              "client": {
                "$ref": "#/components/schemas/NamedRecord"
              }
            }
          }
        ]
      },
      "HoursIntervals": {
        "type": "array",
        "description": "Local [from, to] ranges, 24-hour \"HH:MM\" (\"24:00\" is midnight at the day's end); [] is a day off, two ranges a split shift.",
        "items": {
          "type": "array",
          "minItems": 2,
          "maxItems": 2,
          "items": {
            "type": "string"
          }
        },
        "examples": [
          [
            [
              "10:00",
              "13:00"
            ],
            [
              "14:00",
              "19:00"
            ]
          ]
        ]
      },
      "WeeklyHours": {
        "type": "object",
        "required": [
          "mon",
          "tue",
          "wed",
          "thu",
          "fri",
          "sat",
          "sun"
        ],
        "properties": {
          "mon": {
            "$ref": "#/components/schemas/HoursIntervals"
          },
          "tue": {
            "$ref": "#/components/schemas/HoursIntervals"
          },
          "wed": {
            "$ref": "#/components/schemas/HoursIntervals"
          },
          "thu": {
            "$ref": "#/components/schemas/HoursIntervals"
          },
          "fri": {
            "$ref": "#/components/schemas/HoursIntervals"
          },
          "sat": {
            "$ref": "#/components/schemas/HoursIntervals"
          },
          "sun": {
            "$ref": "#/components/schemas/HoursIntervals"
          }
        }
      },
      "WeeklyHoursInput": {
        "type": "object",
        "description": "The days to change; the others keep their hours.",
        "properties": {
          "mon": {
            "$ref": "#/components/schemas/HoursIntervals"
          },
          "tue": {
            "$ref": "#/components/schemas/HoursIntervals"
          },
          "wed": {
            "$ref": "#/components/schemas/HoursIntervals"
          },
          "thu": {
            "$ref": "#/components/schemas/HoursIntervals"
          },
          "fri": {
            "$ref": "#/components/schemas/HoursIntervals"
          },
          "sat": {
            "$ref": "#/components/schemas/HoursIntervals"
          },
          "sun": {
            "$ref": "#/components/schemas/HoursIntervals"
          }
        },
        "examples": [
          {
            "sat": [
              [
                "10:00",
                "14:00"
              ]
            ],
            "sun": []
          }
        ]
      },
      "CalendarFeature": {
        "type": "string",
        "enum": [
          "team",
          "front_desk",
          "walk_ins",
          "processing",
          "buffers",
          "intake_questions",
          "request_to_book",
          "advance"
        ]
      },
      "CalendarPresetDetail": {
        "type": "object",
        "required": [
          "key",
          "name",
          "description",
          "examples",
          "labels",
          "cta",
          "features",
          "defaults",
          "opening_hours",
          "currency",
          "starter_offerings"
        ],
        "properties": {
          "key": {
            "$ref": "#/components/schemas/CalendarPreset"
          },
          "name": {
            "type": "string",
            "examples": [
              "Salon, barber, nails & beauty"
            ]
          },
          "description": {
            "type": "string",
            "examples": [
              "Services, a team day view, walk-ins"
            ]
          },
          "examples": {
            "type": "string",
            "examples": [
              "Hair, nails, beauty, barbershops"
            ]
          },
          "labels": {
            "$ref": "#/components/schemas/CalendarLabels"
          },
          "cta": {
            "type": "string",
            "description": "The Book button's words on its Kards. A calendar whose services all need approval says \"Request a booking\" instead.",
            "examples": [
              "Book appointment"
            ]
          },
          "features": {
            "type": "array",
            "description": "What this kind switches on.",
            "items": {
              "$ref": "#/components/schemas/CalendarFeature"
            }
          },
          "defaults": {
            "type": "object",
            "description": "Its starting booking rules.",
            "required": [
              "min_notice_minutes",
              "horizon_days",
              "slot_step_minutes",
              "cancellation_cutoff_minutes",
              "require_phone",
              "require_email"
            ],
            "properties": {
              "min_notice_minutes": {
                "type": "integer"
              },
              "horizon_days": {
                "type": "integer"
              },
              "slot_step_minutes": {
                "type": "integer"
              },
              "cancellation_cutoff_minutes": {
                "type": "integer"
              },
              "require_phone": {
                "type": "boolean"
              },
              "require_email": {
                "type": "boolean"
              }
            }
          },
          "opening_hours": {
            "$ref": "#/components/schemas/WeeklyHours"
          },
          "currency": {
            "type": "string"
          },
          "starter_offerings": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "starter",
                "name",
                "category",
                "duration_minutes",
                "price_cents",
                "price_mode",
                "needs_price"
              ],
              "properties": {
                "starter": {
                  "type": "integer",
                  "description": "Its number, for `{\"starter\": 1}` in POST /calendars."
                },
                "name": {
                  "type": "string"
                },
                "category": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "duration_minutes": {
                  "type": "integer"
                },
                "price_cents": {
                  "type": [
                    "integer",
                    "null"
                  ]
                },
                "price_mode": {
                  "type": "string",
                  "enum": [
                    "fixed",
                    "from",
                    "free",
                    "hidden"
                  ]
                },
                "needs_price": {
                  "type": "boolean",
                  "description": "It shows a price, but has none in this currency yet."
                }
              }
            }
          }
        }
      },
      "CalendarSetupInput": {
        "type": "object",
        "required": [
          "preset"
        ],
        "properties": {
          "organization": {
            "type": "string",
            "description": "An organization's slug, to set up its calendar (its admins); the key's own calendar when omitted."
          },
          "preset": {
            "$ref": "#/components/schemas/CalendarPreset"
          },
          "time_zone": {
            "type": "string",
            "description": "An IANA zone, such as Asia/Kolkata."
          },
          "currency": {
            "type": "string",
            "description": "ISO 4217."
          },
          "country": {
            "type": "string",
            "description": "ISO 3166-1 alpha-2; needed to read local phone numbers when the time zone doesn't say."
          },
          "kard": {
            "type": "string",
            "description": "A personal calendar's Kard to book from (a share_link)."
          },
          "team": {
            "type": "array",
            "description": "Organizations: who takes bookings.",
            "items": {
              "type": "object",
              "properties": {
                "kard": {
                  "type": "string",
                  "description": "A team Kard's share_link."
                },
                "name": {
                  "type": "string"
                },
                "phone": {
                  "type": "string"
                },
                "me": {
                  "type": "boolean",
                  "description": "The key's own person."
                }
              }
            },
            "examples": [
              [
                {
                  "kard": "priya"
                },
                {
                  "name": "Rahul"
                },
                {
                  "me": true
                }
              ]
            ]
          },
          "opening_hours": {
            "$ref": "#/components/schemas/WeeklyHoursInput"
          },
          "offerings": {
            "description": "\"starters\" for the kind's starter menu, or rows (a row with `starter` starts from that one).",
            "oneOf": [
              {
                "const": "starters"
              },
              {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "starter": {
                      "type": "integer"
                    },
                    "name": {
                      "type": "string"
                    },
                    "category": {
                      "type": "string"
                    },
                    "duration_minutes": {
                      "type": "integer"
                    },
                    "price_cents": {
                      "type": "integer"
                    },
                    "price_mode": {
                      "type": "string",
                      "enum": [
                        "fixed",
                        "from",
                        "free",
                        "hidden"
                      ]
                    }
                  }
                }
              }
            ]
          },
          "accepting_bookings": {
            "type": "boolean",
            "description": "false keeps online booking off after setup."
          }
        }
      },
      "CalendarSettings": {
        "type": "object",
        "required": [
          "name",
          "preset",
          "kind",
          "labels",
          "time_zone",
          "country",
          "currency",
          "address",
          "accepting_bookings",
          "live",
          "min_notice_minutes",
          "horizon_days",
          "slot_step_minutes",
          "max_upcoming_per_client",
          "require_phone",
          "require_email",
          "cancellation_cutoff_minutes",
          "policy_text",
          "reminder_24h",
          "reminder_2h",
          "reminder_note",
          "notify_owner_online_bookings",
          "digest_minute",
          "digest_time",
          "opening_hours",
          "opening_hours_summary",
          "feed_url"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "preset": {
            "$ref": "#/components/schemas/CalendarPreset"
          },
          "kind": {
            "type": "string",
            "examples": [
              "Salon, barber, nails & beauty"
            ]
          },
          "labels": {
            "$ref": "#/components/schemas/CalendarLabels"
          },
          "time_zone": {
            "type": "string"
          },
          "country": {
            "type": [
              "string",
              "null"
            ]
          },
          "currency": {
            "type": "string"
          },
          "address": {
            "type": [
              "string",
              "null"
            ]
          },
          "accepting_bookings": {
            "type": "boolean",
            "description": "The live switch for online booking."
          },
          "live": {
            "type": "object",
            "required": [
              "bookable_online",
              "kards",
              "kards_showing_book"
            ],
            "properties": {
              "bookable_online": {
                "type": "boolean",
                "description": "There's something clients can book online."
              },
              "kards": {
                "type": "integer"
              },
              "kards_showing_book": {
                "type": "integer"
              }
            }
          },
          "min_notice_minutes": {
            "type": "integer",
            "description": "How soon before a visit clients can book it."
          },
          "horizon_days": {
            "type": "integer",
            "description": "How far ahead clients can book."
          },
          "slot_step_minutes": {
            "type": "integer",
            "description": "Times start every so many minutes."
          },
          "max_upcoming_per_client": {
            "type": "integer"
          },
          "require_phone": {
            "type": "boolean"
          },
          "require_email": {
            "type": "boolean"
          },
          "cancellation_cutoff_minutes": {
            "type": "integer",
            "description": "Until how long before a visit clients can cancel or move it online."
          },
          "policy_text": {
            "type": [
              "string",
              "null"
            ]
          },
          "reminder_24h": {
            "type": "boolean"
          },
          "reminder_2h": {
            "type": "boolean"
          },
          "reminder_note": {
            "type": [
              "string",
              "null"
            ],
            "description": "Added to the reminders."
          },
          "notify_owner_online_bookings": {
            "type": "boolean",
            "description": "Email the owner each online booking."
          },
          "digest_minute": {
            "type": [
              "integer",
              "null"
            ],
            "description": "The morning summary's time, in minutes after local midnight; null for none."
          },
          "digest_time": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "08:00"
            ]
          },
          "opening_hours": {
            "description": "The business's hours; null on a personal calendar (its hours are its person's).",
            "oneOf": [
              {
                "$ref": "#/components/schemas/WeeklyHours"
              },
              {
                "type": "null"
              }
            ]
          },
          "opening_hours_summary": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "Mon–Sat 10:00–19:00"
            ]
          },
          "feed_url": {
            "type": "string",
            "format": "uri",
            "description": "Every booking, for Google, Apple or Outlook. Private."
          }
        }
      },
      "CalendarSettingsInput": {
        "type": "object",
        "description": "Any of these; the rest stay.",
        "properties": {
          "accepting_bookings": {
            "type": "boolean"
          },
          "min_notice_minutes": {
            "type": "integer"
          },
          "horizon_days": {
            "type": "integer",
            "minimum": 1,
            "maximum": 365
          },
          "slot_step_minutes": {
            "type": "integer"
          },
          "max_upcoming_per_client": {
            "type": "integer"
          },
          "require_phone": {
            "type": "boolean"
          },
          "require_email": {
            "type": "boolean"
          },
          "cancellation_cutoff_minutes": {
            "type": "integer"
          },
          "policy_text": {
            "type": [
              "string",
              "null"
            ]
          },
          "reminder_24h": {
            "type": "boolean"
          },
          "reminder_2h": {
            "type": "boolean"
          },
          "reminder_note": {
            "type": [
              "string",
              "null"
            ]
          },
          "notify_owner_online_bookings": {
            "type": "boolean"
          },
          "digest_time": {
            "type": [
              "string",
              "null"
            ],
            "description": "The morning summary's local time, like \"08:00\"; null for none."
          },
          "digest_minute": {
            "type": [
              "integer",
              "null"
            ]
          },
          "name": {
            "type": "string"
          },
          "time_zone": {
            "type": "string"
          },
          "currency": {
            "type": "string",
            "description": "Prices keep their numbers in the new currency (500 stays 500)."
          },
          "country": {
            "type": [
              "string",
              "null"
            ]
          },
          "address": {
            "type": [
              "string",
              "null"
            ]
          },
          "preset": {
            "$ref": "#/components/schemas/CalendarPreset"
          },
          "opening_hours": {
            "$ref": "#/components/schemas/WeeklyHoursInput"
          }
        }
      },
      "FeedLink": {
        "type": "object",
        "required": [
          "feed_url",
          "message"
        ],
        "properties": {
          "feed_url": {
            "type": "string",
            "format": "uri"
          },
          "message": {
            "type": "string"
          }
        }
      },
      "ShareKit": {
        "type": "object",
        "required": [
          "booking_url",
          "front_door",
          "accepting_bookings",
          "bookable_online",
          "kards_showing_book",
          "team_links",
          "whatsapp_url",
          "qr",
          "website",
          "booking_links"
        ],
        "properties": {
          "booking_url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "description": "The booking page of the calendar's main Kard."
          },
          "front_door": {
            "description": "The Kard whose booking page books the whole calendar.",
            "oneOf": [
              {
                "type": "object",
                "required": [
                  "share_link",
                  "name"
                ],
                "properties": {
                  "share_link": {
                    "type": "string"
                  },
                  "name": {
                    "type": "string"
                  }
                }
              },
              {
                "type": "null"
              }
            ]
          },
          "accepting_bookings": {
            "type": "boolean"
          },
          "bookable_online": {
            "type": "boolean"
          },
          "kards_showing_book": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "share_link",
                "name",
                "booking_url",
                "book_label"
              ],
              "properties": {
                "share_link": {
                  "type": "string"
                },
                "name": {
                  "type": "string"
                },
                "booking_url": {
                  "type": "string",
                  "format": "uri"
                },
                "book_label": {
                  "type": [
                    "string",
                    "null"
                  ]
                }
              }
            }
          },
          "team_links": {
            "type": "array",
            "description": "For a team of several, links that book one person.",
            "items": {
              "type": "object",
              "required": [
                "provider",
                "share_link",
                "booking_url"
              ],
              "properties": {
                "provider": {
                  "$ref": "#/components/schemas/NamedRecord"
                },
                "share_link": {
                  "type": "string"
                },
                "booking_url": {
                  "type": "string",
                  "format": "uri"
                }
              }
            }
          },
          "whatsapp_url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "description": "Opens WhatsApp with a message carrying the link."
          },
          "qr": {
            "oneOf": [
              {
                "type": "object",
                "required": [
                  "url",
                  "png"
                ],
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri"
                  },
                  "png": {
                    "type": "string",
                    "description": "The QR code to print, as a data URL."
                  }
                }
              },
              {
                "type": "null"
              }
            ]
          },
          "website": {
            "description": "HTML snippets for a website.",
            "oneOf": [
              {
                "type": "object",
                "required": [
                  "inline",
                  "popup"
                ],
                "properties": {
                  "inline": {
                    "type": "string",
                    "description": "The booking page inside the website."
                  },
                  "popup": {
                    "type": "string",
                    "description": "A button that opens it."
                  }
                }
              },
              {
                "type": "null"
              }
            ]
          },
          "booking_links": {
            "type": "array",
            "description": "The latest 20 single-use links.",
            "items": {
              "$ref": "#/components/schemas/BookingLink"
            }
          }
        }
      },
      "BookingLink": {
        "type": "object",
        "required": [
          "id",
          "url",
          "title",
          "label",
          "offering",
          "provider",
          "max_uses",
          "uses_count",
          "uses_left",
          "expires_at",
          "usable",
          "status",
          "created_at",
          "created_by"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "description": "Send it to the client. Null until the calendar has a Kard to book from."
          },
          "title": {
            "type": "string",
            "examples": [
              "Rahul, monthly trim"
            ]
          },
          "label": {
            "type": [
              "string",
              "null"
            ]
          },
          "offering": {
            "description": "What it books; anything on the menu when null.",
            "oneOf": [
              {
                "$ref": "#/components/schemas/NamedRecord"
              },
              {
                "type": "null"
              }
            ]
          },
          "provider": {
            "description": "Who with; anyone when null.",
            "oneOf": [
              {
                "$ref": "#/components/schemas/NamedRecord"
              },
              {
                "type": "null"
              }
            ]
          },
          "max_uses": {
            "type": "integer"
          },
          "uses_count": {
            "type": "integer"
          },
          "uses_left": {
            "type": "integer"
          },
          "expires_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "usable": {
            "type": "boolean",
            "description": "Someone can book through it right now."
          },
          "status": {
            "type": "string",
            "examples": [
              "used 0 of 1 · works until Sun 13 Jan, 11:59 PM"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "created_by": {
            "oneOf": [
              {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string"
                  }
                }
              },
              {
                "type": "null"
              }
            ]
          }
        }
      },
      "BookingLinkInput": {
        "type": "object",
        "properties": {
          "offering_id": {
            "type": "integer",
            "description": "What it books; anything on the menu when omitted."
          },
          "provider_id": {
            "type": "integer",
            "description": "Who with; anyone when omitted."
          },
          "label": {
            "type": "string",
            "maxLength": 100,
            "description": "Who it's for, to recognise it later."
          },
          "max_uses": {
            "type": "integer",
            "minimum": 1,
            "maximum": 100,
            "default": 1
          },
          "expires_in_days": {
            "type": "integer",
            "minimum": 1,
            "maximum": 365,
            "description": "It works through the end of that local day; never expires when omitted."
          }
        }
      },
      "CalendarBlock": {
        "type": "object",
        "required": [
          "id",
          "kind",
          "whole_calendar",
          "provider",
          "starts_at",
          "ends_at",
          "local_from",
          "local_to",
          "all_day",
          "note",
          "summary"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "kind": {
            "type": "string",
            "description": "`closed`: the whole business.",
            "enum": [
              "break",
              "time_off",
              "blocked",
              "closed"
            ]
          },
          "whole_calendar": {
            "type": "boolean"
          },
          "provider": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/NamedRecord"
              },
              {
                "type": "null"
              }
            ]
          },
          "starts_at": {
            "type": "string",
            "format": "date-time"
          },
          "ends_at": {
            "type": "string",
            "format": "date-time"
          },
          "local_from": {
            "type": "string",
            "format": "date-time"
          },
          "local_to": {
            "type": "string",
            "format": "date-time"
          },
          "all_day": {
            "type": "boolean",
            "description": "From local midnight to local midnight."
          },
          "note": {
            "type": [
              "string",
              "null"
            ]
          },
          "summary": {
            "type": "string",
            "examples": [
              "Wed 9 Jan, 17:00–18:00 (blocked)"
            ]
          }
        }
      },
      "CalendarBlockInput": {
        "type": "object",
        "properties": {
          "provider_id": {
            "description": "One person; omitted or \"all\" closes the whole business.",
            "oneOf": [
              {
                "type": "integer"
              },
              {
                "const": "all"
              }
            ]
          },
          "kind": {
            "type": "string",
            "description": "For one person; a closure is always `closed`.",
            "enum": [
              "break",
              "blocked",
              "time_off"
            ],
            "default": "blocked"
          },
          "note": {
            "type": "string",
            "examples": [
              "Product training",
              "Closed for Diwali"
            ]
          },
          "date": {
            "type": "string",
            "format": "date",
            "description": "The local date; today when omitted."
          },
          "time": {
            "type": "string",
            "description": "The local start, like \"15:30\"; the next quarter hour when omitted."
          },
          "duration": {
            "description": "Minutes (5 to 1440), or \"rest\" for the rest of the day; 60 when omitted.",
            "oneOf": [
              {
                "type": "integer",
                "minimum": 5,
                "maximum": 1440
              },
              {
                "const": "rest"
              }
            ]
          },
          "until": {
            "type": "string",
            "description": "A local end time that day, like \"17:00\", instead of `duration`."
          },
          "starts_at": {
            "type": "string",
            "format": "date-time",
            "description": "With `ends_at`, instead of the local choices."
          },
          "ends_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "MoveInput": {
        "type": "object",
        "required": [
          "direction"
        ],
        "properties": {
          "direction": {
            "type": "string",
            "enum": [
              "up",
              "down"
            ]
          }
        }
      },
      "OfferingQuestion": {
        "type": "object",
        "required": [
          "key",
          "label",
          "kind",
          "required"
        ],
        "properties": {
          "key": {
            "type": "string",
            "description": "Answers are stored by it; send it back to keep a question."
          },
          "label": {
            "type": "string",
            "examples": [
              "Any allergies?"
            ]
          },
          "kind": {
            "type": "string",
            "enum": [
              "text",
              "textarea",
              "select",
              "checkbox"
            ]
          },
          "required": {
            "type": "boolean"
          },
          "options": {
            "type": "array",
            "description": "A select question's choices.",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "MenuOffering": {
        "type": "object",
        "required": [
          "id",
          "name",
          "slug",
          "category",
          "description",
          "active",
          "position",
          "duration_minutes",
          "duration_options",
          "duration_choices",
          "buffer_before_minutes",
          "buffer_after_minutes",
          "processing_offset_minutes",
          "processing_minutes",
          "price_cents",
          "price_mode",
          "price",
          "currency",
          "advance_cents",
          "location_kind",
          "location_details",
          "confirmation_note",
          "visibility",
          "requires_approval",
          "min_notice_minutes",
          "horizon_days",
          "slot_step_minutes",
          "daily_cap",
          "questions",
          "providers",
          "booking_url"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "slug": {
            "type": "string"
          },
          "category": {
            "type": [
              "string",
              "null"
            ]
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "active": {
            "type": "boolean",
            "description": "Switched on (on the booking page, unless it's internal)."
          },
          "position": {
            "type": "integer"
          },
          "duration_minutes": {
            "type": "integer"
          },
          "duration_options": {
            "type": "array",
            "description": "The other lengths a client may pick.",
            "items": {
              "type": "integer"
            }
          },
          "duration_choices": {
            "type": "array",
            "description": "Every length, the default first.",
            "items": {
              "type": "integer"
            }
          },
          "buffer_before_minutes": {
            "type": "integer",
            "description": "Prep time before."
          },
          "buffer_after_minutes": {
            "type": "integer",
            "description": "Cleanup time after."
          },
          "processing_offset_minutes": {
            "type": "integer",
            "description": "When the processing window starts, from the visit's start."
          },
          "processing_minutes": {
            "type": "integer",
            "description": "A window inside the visit when the person is free (colour developing); 0 for none."
          },
          "price_cents": {
            "type": [
              "integer",
              "null"
            ]
          },
          "price_mode": {
            "type": "string",
            "enum": [
              "fixed",
              "from",
              "free",
              "hidden"
            ]
          },
          "price": {
            "type": [
              "string",
              "null"
            ],
            "description": "As clients see it.",
            "examples": [
              "From ₹1,200"
            ]
          },
          "currency": {
            "type": "string"
          },
          "advance_cents": {
            "type": [
              "integer",
              "null"
            ],
            "description": "The advance a client is asked for when booking."
          },
          "location_kind": {
            "type": "string",
            "enum": [
              "at_business",
              "at_client",
              "phone",
              "video",
              "custom"
            ]
          },
          "location_details": {
            "type": [
              "string",
              "null"
            ]
          },
          "confirmation_note": {
            "type": [
              "string",
              "null"
            ],
            "description": "In the client's confirmation."
          },
          "visibility": {
            "type": "string",
            "description": "`public`: on the booking page; `unlisted`: bookable only by its link; `internal`: the front desk only.",
            "enum": [
              "public",
              "unlisted",
              "internal"
            ]
          },
          "requires_approval": {
            "type": "boolean",
            "description": "A request the business approves, rather than an instant booking."
          },
          "min_notice_minutes": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Null for the calendar's rule."
          },
          "horizon_days": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Null for the calendar's rule."
          },
          "slot_step_minutes": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Null for the calendar's rule."
          },
          "daily_cap": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Most bookings of it in a day; null for no limit."
          },
          "questions": {
            "type": "array",
            "description": "Asked when booking (up to 5).",
            "items": {
              "$ref": "#/components/schemas/OfferingQuestion"
            }
          },
          "providers": {
            "type": "array",
            "description": "Who performs it, with their own price and length (null for the offering's).",
            "items": {
              "type": "object",
              "required": [
                "id",
                "name",
                "price_cents",
                "duration_minutes",
                "price"
              ],
              "properties": {
                "id": {
                  "type": "integer"
                },
                "name": {
                  "type": "string"
                },
                "price_cents": {
                  "type": [
                    "integer",
                    "null"
                  ]
                },
                "duration_minutes": {
                  "type": [
                    "integer",
                    "null"
                  ]
                },
                "price": {
                  "type": [
                    "string",
                    "null"
                  ]
                }
              }
            }
          },
          "booking_url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "description": "Its own page on the business's booking link, while a client can reach it (switched on, not internal, online booking live)."
          },
          "upcoming_bookings": {
            "type": "integer",
            "description": "One offering (GET …/offerings/{id}) only."
          },
          "recent_bookings": {
            "type": "integer",
            "description": "In the last 90 days; one offering only."
          },
          "has_bookings": {
            "type": "boolean",
            "description": "Then it can be switched off, but not deleted; one offering only."
          }
        }
      },
      "MenuOfferingEnvelope": {
        "type": "object",
        "required": [
          "offering"
        ],
        "properties": {
          "offering": {
            "$ref": "#/components/schemas/MenuOffering"
          },
          "message": {
            "type": "string",
            "description": "After switching it off or on.",
            "examples": [
              "Blow-dry is off: it's no longer on your booking page."
            ]
          }
        }
      },
      "OfferingInput": {
        "type": "object",
        "description": "Any of these; `null` clears one.",
        "properties": {
          "name": {
            "type": "string"
          },
          "category": {
            "type": [
              "string",
              "null"
            ]
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "duration_minutes": {
            "type": "integer",
            "description": "Steps of 5, up to 12 hours."
          },
          "duration_options": {
            "type": "array",
            "items": {
              "type": "integer"
            }
          },
          "buffer_before_minutes": {
            "type": "integer"
          },
          "buffer_after_minutes": {
            "type": "integer"
          },
          "processing_offset_minutes": {
            "type": "integer",
            "description": "After the start; the window must end within every person's length."
          },
          "processing_minutes": {
            "type": "integer",
            "description": "0 turns processing off."
          },
          "price_cents": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Needed for `fixed` and `from`."
          },
          "price_mode": {
            "type": "string",
            "enum": [
              "fixed",
              "from",
              "free",
              "hidden"
            ]
          },
          "advance_cents": {
            "type": [
              "integer",
              "null"
            ]
          },
          "location_kind": {
            "type": "string",
            "enum": [
              "at_business",
              "at_client",
              "phone",
              "video",
              "custom"
            ]
          },
          "location_details": {
            "type": [
              "string",
              "null"
            ]
          },
          "confirmation_note": {
            "type": [
              "string",
              "null"
            ]
          },
          "visibility": {
            "type": "string",
            "enum": [
              "public",
              "unlisted",
              "internal"
            ]
          },
          "requires_approval": {
            "type": "boolean"
          },
          "active": {
            "type": "boolean",
            "description": "false switches it off; true back on."
          },
          "min_notice_minutes": {
            "type": [
              "integer",
              "null"
            ]
          },
          "horizon_days": {
            "type": [
              "integer",
              "null"
            ]
          },
          "slot_step_minutes": {
            "type": [
              "integer",
              "null"
            ]
          },
          "daily_cap": {
            "type": [
              "integer",
              "null"
            ]
          },
          "questions": {
            "type": "array",
            "maxItems": 5,
            "description": "Replaces the whole list; select questions need `options`.",
            "items": {
              "type": "object",
              "required": [
                "label",
                "kind",
                "required"
              ],
              "properties": {
                "key": {
                  "type": "string"
                },
                "label": {
                  "type": "string"
                },
                "kind": {
                  "type": "string",
                  "enum": [
                    "text",
                    "textarea",
                    "select",
                    "checkbox"
                  ]
                },
                "required": {
                  "type": "boolean"
                },
                "options": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                }
              }
            }
          },
          "providers": {
            "type": "array",
            "description": "Who performs it, replacing the whole list; everyone on the team when omitted on create.",
            "items": {
              "type": "object",
              "required": [
                "id"
              ],
              "properties": {
                "id": {
                  "type": "integer"
                },
                "price_cents": {
                  "type": [
                    "integer",
                    "null"
                  ]
                },
                "duration_minutes": {
                  "type": [
                    "integer",
                    "null"
                  ]
                }
              }
            }
          }
        }
      },
      "MenuImport": {
        "type": "object",
        "required": [
          "id",
          "status",
          "source_kind",
          "source_filename",
          "source_url",
          "has_source",
          "created_at",
          "expires_at",
          "stalled",
          "read_at"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "status": {
            "type": "string",
            "enum": [
              "queued",
              "reading",
              "ready",
              "failed",
              "confirmed",
              "discarded",
              "expired"
            ]
          },
          "source_kind": {
            "type": "string",
            "description": "`url` until the link is fetched.",
            "enum": [
              "text",
              "photo",
              "pdf",
              "url"
            ]
          },
          "source_filename": {
            "type": [
              "string",
              "null"
            ]
          },
          "source_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "has_source": {
            "type": "boolean",
            "description": "Its photo or PDF can be fetched (GET …/source)."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "stalled": {
            "type": "boolean",
            "description": "A read that died; start again."
          },
          "read_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "error": {
            "type": "object",
            "description": "Failed or stalled, in the web's words.",
            "required": [
              "code",
              "title",
              "message",
              "detail"
            ],
            "properties": {
              "code": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "unreadable",
                  "unreadable_file",
                  "download_failed",
                  "budget",
                  "provider",
                  "stalled",
                  null
                ]
              },
              "title": {
                "type": "string"
              },
              "message": {
                "type": "string",
                "description": "What happened and what to do next."
              },
              "detail": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          "added_count": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Confirmed only."
          },
          "confirmed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "rows": {
            "type": "array",
            "description": "Ready, one import only.",
            "items": {
              "$ref": "#/components/schemas/MenuImportRow"
            }
          },
          "summary": {
            "type": "object",
            "required": [
              "found",
              "included",
              "guessed",
              "attention",
              "duplicates",
              "no_price",
              "categories"
            ],
            "properties": {
              "found": {
                "type": "integer"
              },
              "included": {
                "type": "integer"
              },
              "guessed": {
                "type": "integer"
              },
              "attention": {
                "type": "integer"
              },
              "duplicates": {
                "type": "integer"
              },
              "no_price": {
                "type": "integer"
              },
              "categories": {
                "type": "integer"
              }
            }
          },
          "currency": {
            "type": "string"
          },
          "detected_currency": {
            "type": [
              "string",
              "null"
            ]
          },
          "currency_note": {
            "type": [
              "string",
              "null"
            ],
            "description": "When the list's prices are in another currency; amounts are copied as written."
          }
        }
      },
      "MenuImportRow": {
        "type": "object",
        "required": [
          "key",
          "included",
          "name",
          "category",
          "duration_minutes",
          "duration_guessed",
          "price_cents",
          "price_mode",
          "price_missing",
          "price_text",
          "attention",
          "note",
          "duplicate",
          "duplicate_name"
        ],
        "properties": {
          "key": {
            "type": "string"
          },
          "included": {
            "type": "boolean",
            "description": "The draft's choice."
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "category": {
            "type": [
              "string",
              "null"
            ]
          },
          "duration_minutes": {
            "type": [
              "integer",
              "null"
            ]
          },
          "duration_guessed": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "Not on the list; check it."
          },
          "price_cents": {
            "type": [
              "integer",
              "null"
            ]
          },
          "price_mode": {
            "type": [
              "string",
              "null"
            ]
          },
          "price_missing": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "price_text": {
            "type": [
              "string",
              "null"
            ],
            "description": "As written."
          },
          "attention": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "Something to check (`note` says what)."
          },
          "note": {
            "type": [
              "string",
              "null"
            ]
          },
          "duplicate": {
            "type": [
              "string",
              "null"
            ],
            "description": "`menu`: already on the menu; `list`: twice on the list. Left out by default.",
            "enum": [
              "menu",
              "list",
              null
            ]
          },
          "duplicate_name": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "MenuImportEnvelope": {
        "type": "object",
        "required": [
          "menu_import"
        ],
        "properties": {
          "menu_import": {
            "$ref": "#/components/schemas/MenuImport"
          }
        }
      },
      "LoginStatus": {
        "type": "string",
        "description": "`joined`: has a login; `admin`: an admin's account; `invited` / `invite_expired`: an open invitation; `none`.",
        "enum": [
          "joined",
          "admin",
          "invited",
          "invite_expired",
          "none"
        ]
      },
      "TeamProvider": {
        "type": "object",
        "required": [
          "id",
          "name",
          "first_name",
          "title",
          "phone",
          "email",
          "color",
          "active",
          "bookable_online",
          "takes_online_bookings",
          "daily_cap",
          "position",
          "yours",
          "kard",
          "hours_summary",
          "offerings"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "first_name": {
            "type": "string"
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "phone": {
            "type": [
              "string",
              "null"
            ]
          },
          "email": {
            "type": [
              "string",
              "null"
            ]
          },
          "color": {
            "type": "string",
            "enum": [
              "clay",
              "amber",
              "ochre",
              "olive",
              "sage",
              "teal",
              "sky",
              "indigo",
              "plum",
              "rose"
            ]
          },
          "active": {
            "type": "boolean",
            "description": "On the team (switched on)."
          },
          "bookable_online": {
            "type": "boolean",
            "description": "The switch for clients picking them online."
          },
          "takes_online_bookings": {
            "type": "boolean",
            "description": "Switched on, active, and doing something on the booking page."
          },
          "daily_cap": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Most online bookings in a day; null for no limit."
          },
          "position": {
            "type": "integer"
          },
          "yours": {
            "type": "boolean",
            "description": "It's the key's own person."
          },
          "kard": {
            "description": "The Kard showing \"Book with …\" for them.",
            "oneOf": [
              {
                "type": "object",
                "required": [
                  "share_link",
                  "name",
                  "url"
                ],
                "properties": {
                  "share_link": {
                    "type": "string"
                  },
                  "name": {
                    "type": "string"
                  },
                  "url": {
                    "type": "string",
                    "format": "uri"
                  }
                }
              },
              {
                "type": "null"
              }
            ]
          },
          "hours_summary": {
            "type": [
              "string",
              "null"
            ],
            "examples": [
              "Mon–Fri 09:00–18:00"
            ]
          },
          "offerings": {
            "type": "array",
            "description": "What they do, with their own price and minutes (null for the menu's).",
            "items": {
              "type": "object",
              "required": [
                "id",
                "name",
                "active",
                "price_cents",
                "duration_minutes",
                "price",
                "duration"
              ],
              "properties": {
                "id": {
                  "type": "integer"
                },
                "name": {
                  "type": "string"
                },
                "active": {
                  "type": "boolean"
                },
                "price_cents": {
                  "type": [
                    "integer",
                    "null"
                  ]
                },
                "duration_minutes": {
                  "type": [
                    "integer",
                    "null"
                  ]
                },
                "price": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "duration": {
                  "type": "string",
                  "examples": [
                    "1 hr 30 min"
                  ]
                }
              }
            }
          },
          "login": {
            "description": "Admins only: where their login stands; null on a personal calendar.",
            "oneOf": [
              {
                "type": "object",
                "required": [
                  "status",
                  "role",
                  "email",
                  "invitation"
                ],
                "properties": {
                  "status": {
                    "$ref": "#/components/schemas/LoginStatus"
                  },
                  "role": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "admin",
                      "front_desk",
                      "staff",
                      null
                    ]
                  },
                  "email": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "invitation": {
                    "oneOf": [
                      {
                        "type": "object",
                        "required": [
                          "id",
                          "email",
                          "role",
                          "status",
                          "expires_at"
                        ],
                        "properties": {
                          "id": {
                            "type": "integer"
                          },
                          "email": {
                            "type": "string"
                          },
                          "role": {
                            "type": "string",
                            "enum": [
                              "front_desk",
                              "staff"
                            ]
                          },
                          "status": {
                            "$ref": "#/components/schemas/InvitationStatus"
                          },
                          "expires_at": {
                            "type": "string",
                            "format": "date-time"
                          }
                        }
                      },
                      {
                        "type": "null"
                      }
                    ]
                  }
                }
              },
              {
                "type": "null"
              }
            ]
          }
        }
      },
      "TeamProviderDetail": {
        "allOf": [
          {
            "$ref": "#/components/schemas/TeamProvider"
          },
          {
            "type": "object",
            "required": [
              "weekly_hours",
              "special_days",
              "time_off",
              "links"
            ],
            "properties": {
              "weekly_hours": {
                "$ref": "#/components/schemas/WeeklyHours"
              },
              "special_days": {
                "type": "array",
                "description": "Upcoming.",
                "items": {
                  "$ref": "#/components/schemas/SpecialDay"
                }
              },
              "time_off": {
                "type": "array",
                "description": "Upcoming.",
                "items": {
                  "$ref": "#/components/schemas/TimeOff"
                }
              },
              "links": {
                "type": "object",
                "description": "Private: anyone with them sees this person's bookings.",
                "required": [
                  "my_day_url",
                  "feed_url"
                ],
                "properties": {
                  "my_day_url": {
                    "type": "string",
                    "format": "uri",
                    "description": "Their day on a phone, read-only, no login."
                  },
                  "feed_url": {
                    "type": "string",
                    "format": "uri",
                    "description": "Their bookings, for Google, Apple or Outlook."
                  }
                }
              }
            }
          }
        ]
      },
      "SuggestedKard": {
        "type": "object",
        "required": [
          "share_link",
          "name",
          "title",
          "url"
        ],
        "properties": {
          "share_link": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "url": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "ProviderInput": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "title": {
            "type": [
              "string",
              "null"
            ]
          },
          "phone": {
            "type": [
              "string",
              "null"
            ]
          },
          "email": {
            "type": [
              "string",
              "null"
            ]
          },
          "color": {
            "type": "string",
            "enum": [
              "clay",
              "amber",
              "ochre",
              "olive",
              "sage",
              "teal",
              "sky",
              "indigo",
              "plum",
              "rose"
            ]
          },
          "kard": {
            "type": [
              "string",
              "null"
            ],
            "description": "A team Kard's share_link; null unlinks it."
          },
          "bookable_online": {
            "type": "boolean"
          },
          "active": {
            "type": "boolean",
            "description": "false switches them off."
          },
          "daily_cap": {
            "type": [
              "integer",
              "null"
            ]
          },
          "offering_ids": {
            "type": "array",
            "description": "What they do (replaces the list).",
            "items": {
              "type": "integer"
            }
          }
        }
      },
      "ProviderHours": {
        "type": "object",
        "required": [
          "weekly_hours",
          "summary",
          "bookable_online",
          "takes_online_bookings",
          "opening_hours",
          "opening_hours_summary",
          "time_zone"
        ],
        "properties": {
          "weekly_hours": {
            "$ref": "#/components/schemas/WeeklyHours"
          },
          "summary": {
            "type": [
              "string",
              "null"
            ]
          },
          "bookable_online": {
            "type": "boolean"
          },
          "takes_online_bookings": {
            "type": "boolean"
          },
          "opening_hours": {
            "$ref": "#/components/schemas/WeeklyHours"
          },
          "opening_hours_summary": {
            "type": [
              "string",
              "null"
            ]
          },
          "time_zone": {
            "type": "string"
          }
        }
      },
      "SpecialDay": {
        "type": "object",
        "required": [
          "id",
          "date",
          "day_off",
          "hours",
          "note",
          "summary"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "date": {
            "type": "string",
            "format": "date"
          },
          "day_off": {
            "type": "boolean"
          },
          "hours": {
            "$ref": "#/components/schemas/HoursIntervals"
          },
          "note": {
            "type": [
              "string",
              "null"
            ]
          },
          "summary": {
            "type": "string",
            "examples": [
              "12:00–20:00",
              "day off"
            ]
          }
        }
      },
      "SpecialDayInput": {
        "type": "object",
        "required": [
          "date"
        ],
        "properties": {
          "date": {
            "type": "string",
            "format": "date"
          },
          "hours": {
            "$ref": "#/components/schemas/HoursIntervals"
          },
          "day_off": {
            "type": "boolean"
          },
          "note": {
            "type": "string",
            "examples": [
              "Late shift"
            ]
          }
        }
      },
      "TimeOff": {
        "type": "object",
        "required": [
          "id",
          "kind",
          "starts_at",
          "ends_at",
          "local_from",
          "local_to",
          "all_day",
          "note",
          "summary",
          "bookings_to_move"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "kind": {
            "type": "string",
            "enum": [
              "time_off",
              "break",
              "blocked"
            ]
          },
          "starts_at": {
            "type": "string",
            "format": "date-time"
          },
          "ends_at": {
            "type": "string",
            "format": "date-time"
          },
          "local_from": {
            "type": "string",
            "format": "date-time"
          },
          "local_to": {
            "type": "string",
            "format": "date-time"
          },
          "all_day": {
            "type": "boolean"
          },
          "note": {
            "type": [
              "string",
              "null"
            ]
          },
          "summary": {
            "type": "string",
            "examples": [
              "Mon 14 Jan – Fri 18 Jan (time off)"
            ]
          },
          "bookings_to_move": {
            "type": "integer",
            "description": "Upcoming bookings with this person inside it."
          }
        }
      },
      "TimeOffInput": {
        "type": "object",
        "required": [
          "starts_on"
        ],
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "time_off",
              "break",
              "blocked"
            ],
            "default": "time_off"
          },
          "starts_on": {
            "type": "string",
            "format": "date"
          },
          "ends_on": {
            "type": "string",
            "format": "date",
            "description": "The same day when omitted."
          },
          "all_day": {
            "type": "boolean",
            "description": "All day unless times are given."
          },
          "start_time": {
            "type": "string",
            "examples": [
              "13:00"
            ]
          },
          "end_time": {
            "type": "string",
            "examples": [
              "14:00"
            ]
          },
          "note": {
            "type": "string"
          }
        }
      },
      "LoginPerson": {
        "type": "object",
        "required": [
          "name",
          "email"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "email": {
            "type": "string"
          }
        }
      },
      "CalendarLogin": {
        "type": "object",
        "required": [
          "id",
          "role",
          "user",
          "provider"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "description": "For DELETE …/logins/{id}."
          },
          "role": {
            "type": "string",
            "enum": [
              "front_desk",
              "staff"
            ]
          },
          "user": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/LoginPerson"
              },
              {
                "type": "null"
              }
            ]
          },
          "provider": {
            "description": "The team member it signs in as, if any.",
            "oneOf": [
              {
                "$ref": "#/components/schemas/NamedRecord"
              },
              {
                "type": "null"
              }
            ]
          }
        }
      },
      "InvitationStatus": {
        "type": "string",
        "enum": [
          "pending",
          "expired",
          "accepted",
          "revoked"
        ]
      },
      "StaffInvitation": {
        "type": "object",
        "required": [
          "id",
          "email",
          "role",
          "provider",
          "status",
          "expires_at",
          "last_sent_at",
          "invited_by",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "email": {
            "type": "string"
          },
          "role": {
            "type": "string",
            "enum": [
              "front_desk",
              "staff"
            ]
          },
          "provider": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/NamedRecord"
              },
              {
                "type": "null"
              }
            ]
          },
          "status": {
            "$ref": "#/components/schemas/InvitationStatus"
          },
          "expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "last_sent_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "invited_by": {
            "oneOf": [
              {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string"
                  }
                }
              },
              {
                "type": "null"
              }
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "WebhookEvent": {
        "type": "string",
        "enum": [
          "booking.created",
          "booking.rescheduled",
          "booking.cancelled",
          "booking.status_changed"
        ]
      },
      "CalendarWebhook": {
        "type": "object",
        "required": [
          "id",
          "url",
          "host",
          "events",
          "secret",
          "enabled",
          "disabled_at",
          "disabled_reason",
          "consecutive_failures",
          "last_attempted_at",
          "last_delivered_at",
          "last_status_code",
          "last_error",
          "test",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "host": {
            "type": "string"
          },
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WebhookEvent"
            }
          },
          "secret": {
            "type": "string",
            "description": "The `whsec_` signing secret (see the `booking` webhook)."
          },
          "enabled": {
            "type": "boolean"
          },
          "disabled_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "disabled_reason": {
            "type": [
              "string",
              "null"
            ],
            "description": "Why it was switched off (after failing again and again)."
          },
          "consecutive_failures": {
            "type": "integer"
          },
          "last_attempted_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "last_delivered_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "last_status_code": {
            "type": [
              "integer",
              "null"
            ]
          },
          "last_error": {
            "type": [
              "string",
              "null"
            ]
          },
          "test": {
            "type": "object",
            "description": "The latest test.",
            "required": [
              "state",
              "requested_at",
              "tested_at",
              "status_code",
              "duration_ms",
              "error"
            ],
            "properties": {
              "state": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "sending",
                  "stalled",
                  "delivered",
                  "failed",
                  null
                ]
              },
              "requested_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "tested_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "status_code": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "duration_ms": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "error": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "WebhookInput": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "description": "A public https:// address."
          },
          "events": {
            "type": "array",
            "description": "Every event when omitted or empty.",
            "items": {
              "$ref": "#/components/schemas/WebhookEvent"
            }
          },
          "enabled": {
            "type": "boolean"
          }
        }
      },
      "ClientImportInput": {
        "type": "object",
        "properties": {
          "source": {
            "type": "string",
            "enum": [
              "csv",
              "contacts"
            ],
            "default": "csv"
          },
          "csv": {
            "type": "string",
            "description": "The spreadsheet's text (or upload it as `file`)."
          },
          "mapping": {
            "type": "array",
            "description": "What each column is, in order (else guessed from the headings).",
            "items": {
              "type": "string",
              "enum": [
                "name",
                "first_name",
                "last_name",
                "phone",
                "email",
                "notes",
                "skip"
              ]
            }
          },
          "country": {
            "type": "string",
            "description": "Reads local phone numbers; the calendar's country when omitted."
          },
          "kard_ids": {
            "type": "array",
            "description": "`contacts`: the Kards whose contacts to import; all of them when omitted.",
            "items": {
              "type": "integer"
            }
          },
          "dry_run": {
            "type": "boolean",
            "description": "Preview only; nothing is imported."
          }
        }
      },
      "ClientImportPreview": {
        "type": "object",
        "required": [
          "source",
          "counts",
          "rows",
          "rows_total",
          "problems",
          "too_many_rows"
        ],
        "properties": {
          "source": {
            "type": "string",
            "enum": [
              "csv",
              "contacts"
            ]
          },
          "headings": {
            "type": "array",
            "description": "csv",
            "items": {
              "type": "string"
            }
          },
          "mapping": {
            "type": "array",
            "description": "csv",
            "items": {
              "type": "string"
            }
          },
          "mapping_guessed": {
            "type": "boolean",
            "description": "csv"
          },
          "country": {
            "type": [
              "string",
              "null"
            ]
          },
          "kard_ids": {
            "type": "array",
            "description": "contacts",
            "items": {
              "type": "integer"
            }
          },
          "counts": {
            "type": "object",
            "required": [
              "create",
              "update",
              "skip"
            ],
            "properties": {
              "create": {
                "type": "integer"
              },
              "update": {
                "type": "integer"
              },
              "skip": {
                "type": "integer"
              }
            }
          },
          "rows": {
            "type": "array",
            "description": "The first 200 rows.",
            "items": {
              "type": "object",
              "required": [
                "label",
                "action",
                "reason",
                "name",
                "phone",
                "email"
              ],
              "properties": {
                "label": {
                  "type": "string",
                  "examples": [
                    "Row 2"
                  ]
                },
                "action": {
                  "type": "string",
                  "description": "`update` fills an existing client's blanks.",
                  "enum": [
                    "create",
                    "update",
                    "skip"
                  ]
                },
                "reason": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "name": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "phone": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "email": {
                  "type": [
                    "string",
                    "null"
                  ]
                }
              }
            }
          },
          "rows_total": {
            "type": "integer"
          },
          "problems": {
            "type": "array",
            "description": "What still needs choosing before it can run.",
            "items": {
              "type": "string"
            }
          },
          "too_many_rows": {
            "type": "boolean"
          }
        }
      },
      "ClientImportResult": {
        "type": "object",
        "required": [
          "created",
          "updated",
          "skipped"
        ],
        "properties": {
          "created": {
            "type": "integer"
          },
          "updated": {
            "type": "integer"
          },
          "skipped": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "label",
                "name",
                "reason"
              ],
              "properties": {
                "label": {
                  "type": "string"
                },
                "name": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "reason": {
                  "type": [
                    "string",
                    "null"
                  ]
                }
              }
            }
          }
        }
      },
      "SyncService": {
        "type": "string",
        "description": "`google`: Google Calendar; `microsoft`: Outlook.",
        "enum": [
          "google",
          "microsoft"
        ]
      },
      "CalendarConnection": {
        "type": "object",
        "required": [
          "service",
          "name",
          "account_email",
          "status",
          "checks_busy_times",
          "writes_events",
          "busy_calendar_ids",
          "destination_calendar_id",
          "add_meet_links",
          "invite_clients",
          "calendars",
          "calendars_listed_at",
          "calendar_list_failed",
          "allowed",
          "upcoming_not_in_calendar",
          "last_synced_at",
          "last_error",
          "last_error_at"
        ],
        "properties": {
          "service": {
            "$ref": "#/components/schemas/SyncService"
          },
          "name": {
            "type": "string",
            "examples": [
              "Google Calendar"
            ]
          },
          "account_email": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": "string",
            "description": "`needs_reauth`: connect again at `connect_url`.",
            "enum": [
              "connected",
              "needs_reauth",
              "disconnected"
            ]
          },
          "checks_busy_times": {
            "type": "boolean",
            "description": "Busy times in `busy_calendar_ids` block bookable time."
          },
          "writes_events": {
            "type": "boolean",
            "description": "New bookings are added to `destination_calendar_id`."
          },
          "busy_calendar_ids": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "destination_calendar_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "add_meet_links": {
            "type": "boolean"
          },
          "invite_clients": {
            "type": "boolean"
          },
          "calendars": {
            "type": "array",
            "description": "The account's calendars, to choose from.",
            "items": {
              "type": "object",
              "required": [
                "id",
                "name",
                "primary"
              ],
              "properties": {
                "id": {
                  "type": "string"
                },
                "name": {
                  "type": "string"
                },
                "primary": {
                  "type": "boolean"
                }
              }
            }
          },
          "calendars_listed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "calendar_list_failed": {
            "type": "boolean",
            "description": "The service couldn't list them just now; these are from `calendars_listed_at`."
          },
          "allowed": {
            "type": "object",
            "description": "What the person allowed when connecting (connect again to change it).",
            "required": [
              "busy_times",
              "add_bookings",
              "list_calendars"
            ],
            "properties": {
              "busy_times": {
                "type": "boolean"
              },
              "add_bookings": {
                "type": "boolean"
              },
              "list_calendars": {
                "type": "boolean"
              }
            }
          },
          "upcoming_not_in_calendar": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Upcoming bookings not added yet (POST …/add_upcoming adds them); null when it doesn't add bookings."
          },
          "last_synced_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "last_error": {
            "type": [
              "string",
              "null"
            ]
          },
          "last_error_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "CalendarSyncInput": {
        "type": "object",
        "properties": {
          "busy_calendar_ids": {
            "type": [
              "array",
              "null"
            ],
            "maxItems": 10,
            "description": "The calendars whose busy times block; [] or null for none.",
            "items": {
              "type": "string"
            }
          },
          "destination_calendar_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Where new bookings go; null stops adding them."
          },
          "add_meet_links": {
            "type": [
              "boolean",
              "null"
            ]
          },
          "invite_clients": {
            "type": [
              "boolean",
              "null"
            ]
          }
        }
      }
    }
  }
}