{
  "openapi": "3.1.0",
  "info": {
    "title": "Mind the Front API",
    "version": "2026-09-21",
    "description": "Two contours: public endpoints the widget calls without authentication, and owner endpoints your tooling calls with the machine token. Internal endpoints — health, the calendar OAuth callback, Google sign-in and the Telegram exchange — are deliberately absent."
  },
  "tags": [
    {
      "name": "public",
      "description": "No authentication. Called by the widget on your visitors' pages."
    },
    {
      "name": "auth",
      "description": "Account and session."
    },
    {
      "name": "owner",
      "description": "Configs and requests of one business."
    },
    {
      "name": "calendar",
      "description": "The owner's calendar connection."
    }
  ],
  "paths": {
    "/api/public/configs/{publishableKey}": {
      "servers": [
        {
          "url": "https://mindthefront.com"
        }
      ],
      "get": {
        "operationId": "getPublishedConfig",
        "summary": "Fetch the published config for a business",
        "description": "What the widget loads on mount. ETagged and publicly cacheable; publishing a new version invalidates it at once.",
        "tags": [
          "public"
        ],
        "responses": {
          "200": {
            "description": "The published config document.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConfigDocument"
                }
              }
            }
          },
          "304": {
            "description": "Not modified."
          },
          "402": {
            "description": "The widget is unavailable: the business has reached its plan limit (`GET /api/businesses/{businessId}/plan` says which). The widget does not mount.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "No such key, or the business has never published.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Per-IP read limit exceeded: 60 requests per minute by default.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "publishableKey",
            "in": "path",
            "required": true,
            "description": "Public key of the business, shown in the console.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "If-None-Match",
            "in": "header",
            "required": false,
            "description": "Conditional request; a matching ETag returns 304.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "x-example": "curl https://mindthefront.com/api/public/configs/pk_live_8f21",
        "security": []
      }
    },
    "/api/public/conversations": {
      "servers": [
        {
          "url": "https://mindthefront.com"
        }
      ],
      "post": {
        "operationId": "createConversation",
        "summary": "Start a visitor conversation",
        "description": "Issues the visitor token that ties a dialogue to a business.",
        "tags": [
          "public"
        ],
        "responses": {
          "201": {
            "description": "`{ \"visitorToken\": \"...\" }`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConversationResponse"
                }
              }
            }
          },
          "400": {
            "description": "A required header, parameter or field is missing or fails its constraints.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Unknown publishable key.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Per-IP write limit exceeded: 10 requests per minute by default.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateConversationRequest"
              }
            }
          }
        }
      }
    },
    "/api/public/submissions": {
      "servers": [
        {
          "url": "https://mindthefront.com"
        }
      ],
      "post": {
        "operationId": "submitRequest",
        "summary": "Submit a request",
        "description": "Values are validated against the schema of the published config.",
        "tags": [
          "public"
        ],
        "responses": {
          "201": {
            "description": "The stored request with its status.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SubmissionResponse"
                }
              }
            }
          },
          "400": {
            "description": "A required header, parameter or field is missing or fails its constraints.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "402": {
            "description": "The business has reached its plan limit, including the allowance for conversations started before it; the request is not stored.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Unknown visitor token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "The chosen time has just been taken; `field` names the appointment field.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Values do not satisfy the published schema.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Per-IP write limit exceeded: 10 requests per minute by default.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SubmitRequest"
              }
            }
          }
        }
      }
    },
    "/api/public/submissions/latest": {
      "servers": [
        {
          "url": "https://mindthefront.com"
        }
      ],
      "get": {
        "operationId": "latestSubmission",
        "summary": "Read the visitor's latest request",
        "description": "The visitor's latest request and its status. The token goes in a header, not in the query string.",
        "tags": [
          "public"
        ],
        "responses": {
          "200": {
            "description": "The request with its current status.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SubmissionResponse"
                }
              }
            }
          },
          "400": {
            "description": "A required header, parameter or field is missing or fails its constraints.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "No request for this token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Per-IP read limit exceeded: 60 requests per minute by default.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "security": [
          {
            "visitorToken": []
          }
        ]
      }
    },
    "/api/public/availability": {
      "servers": [
        {
          "url": "https://mindthefront.com"
        }
      ],
      "get": {
        "operationId": "listAvailability",
        "summary": "List free slots for an appointment field",
        "description": "The declared schedule minus the owner's calendar, over a 14-day horizon. `sourceUnavailable: true` means the grid was built from the schedule alone and some of it may already be taken.",
        "tags": [
          "public"
        ],
        "responses": {
          "200": {
            "description": "`slots`, `timeZone`, `durationMinutes`, `sourceUnavailable`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AvailabilityResponse"
                }
              }
            }
          },
          "400": {
            "description": "A required header, parameter or field is missing or fails its constraints.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Unknown key, or a field that is not an appointment field.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Per-IP read limit exceeded: 60 requests per minute by default.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "publishableKey",
            "in": "query",
            "required": true,
            "description": "Business key.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "field",
            "in": "query",
            "required": true,
            "description": "Name of a `format: date-time` field in the published config. Anything else returns 404.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "security": []
      }
    },
    "/api/public/showcase": {
      "servers": [
        {
          "url": "https://mindthefront.com"
        }
      ],
      "get": {
        "operationId": "listShowcase",
        "summary": "List the examples shown on the site",
        "description": "Publishable key, business name and the description each example was generated from.",
        "tags": [
          "public"
        ],
        "responses": {
          "200": {
            "description": "Examples in display order; an empty list when the showcase is empty.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/ShowcaseEntry"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Per-IP read limit exceeded: 60 requests per minute by default.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "security": []
      }
    },
    "/api/auth/register": {
      "servers": [
        {
          "url": "https://console.mindthefront.com"
        }
      ],
      "post": {
        "operationId": "register",
        "summary": "Start creating an owner account",
        "description": "Sends a confirmation link to the email; the account and its first business are created when the link is confirmed (`confirmRegistration`). A server without email configured creates the account at once and answers 201.",
        "tags": [
          "auth"
        ],
        "responses": {
          "201": {
            "description": "Email is not configured on this server: the account and its business summary; sets the session cookie.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MeResponse"
                }
              }
            }
          },
          "202": {
            "description": "The confirmation link is on its way. No account exists until it is confirmed."
          },
          "400": {
            "description": "A required header, parameter or field is missing or fails its constraints.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "An account with this email already exists; `field` is `email`. No email is sent.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Registration limit exceeded: 8 per minute per IP or 3 per hour per email by default.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RegisterRequest"
              }
            }
          }
        }
      }
    },
    "/api/auth/register/confirm": {
      "servers": [
        {
          "url": "https://console.mindthefront.com"
        }
      ],
      "post": {
        "operationId": "confirmRegistration",
        "summary": "Confirm the email and create the account",
        "description": "Takes the token from the confirmation link (the part after `#verify=`). Creates the account and its first business and signs this client in.",
        "tags": [
          "auth"
        ],
        "responses": {
          "201": {
            "description": "The account and its business summary; sets the session cookie.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MeResponse"
                }
              }
            }
          },
          "400": {
            "description": "The link is invalid, used or expired.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "An account with this email already exists; `field` is `email`.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Login limit exceeded: 8 per minute per IP by default.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ConfirmRegistrationRequest"
              }
            }
          }
        }
      }
    },
    "/api/auth/login": {
      "servers": [
        {
          "url": "https://console.mindthefront.com"
        }
      ],
      "post": {
        "operationId": "login",
        "summary": "Sign in",
        "description": "Sets the session cookie.",
        "tags": [
          "auth"
        ],
        "responses": {
          "200": {
            "description": "The account and its business summary; sets the session cookie.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MeResponse"
                }
              }
            }
          },
          "400": {
            "description": "A required header, parameter or field is missing or fails its constraints.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "Wrong email or password.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Login limit exceeded: 8 per minute per IP or 5 per hour per email by default.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LoginRequest"
              }
            }
          }
        }
      }
    },
    "/api/auth/logout": {
      "servers": [
        {
          "url": "https://console.mindthefront.com"
        }
      ],
      "post": {
        "operationId": "logout",
        "summary": "Sign out",
        "description": "Invalidates the session and clears the cookie.",
        "tags": [
          "auth"
        ],
        "responses": {
          "204": {
            "description": "Signed out."
          },
          "401": {
            "description": "No owner session.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "security": [
          {
            "ownerSession": []
          }
        ]
      }
    },
    "/api/auth/me": {
      "servers": [
        {
          "url": "https://console.mindthefront.com"
        }
      ],
      "get": {
        "operationId": "currentAccount",
        "summary": "Read the signed-in account",
        "description": "Account id, email and the business summary, including its publishable key. Session only; with a machine token use `GET /api/businesses/current`.",
        "tags": [
          "auth"
        ],
        "responses": {
          "200": {
            "description": "Account and business summary.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MeResponse"
                }
              }
            }
          },
          "401": {
            "description": "No owner session.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "security": [
          {
            "ownerSession": []
          }
        ]
      }
    },
    "/api/auth/account": {
      "servers": [
        {
          "url": "https://console.mindthefront.com"
        }
      ],
      "delete": {
        "operationId": "deleteAccount",
        "summary": "Delete the account",
        "description": "Deletes the account at once with its business, configs, conversations, requests and calendar connection; Google access to the calendar is revoked first. The machine token stops working and the widget stops showing. Only a signed-in owner can do it: the machine token cannot.",
        "tags": [
          "auth"
        ],
        "responses": {
          "204": {
            "description": "Deleted; clears the session cookie."
          },
          "400": {
            "description": "The email does not match the account, or the body fails its constraints.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "No owner session. The machine token is not accepted here.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "security": [
          {
            "ownerSession": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DeleteAccountRequest"
              }
            }
          }
        }
      }
    },
    "/api/auth/password-reset": {
      "servers": [
        {
          "url": "https://console.mindthefront.com"
        }
      ],
      "post": {
        "operationId": "requestPasswordReset",
        "summary": "Request a password reset",
        "description": "If an account uses this email, a link to set a new password is sent to it. The response is the same whether or not the account exists. The link works once, for 30 minutes.",
        "tags": [
          "auth"
        ],
        "responses": {
          "202": {
            "description": "Accepted. Sent or not, the response does not say."
          },
          "400": {
            "description": "A required header, parameter or field is missing or fails its constraints.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Reset limit exceeded: 5 per minute per IP or 3 per hour per email by default.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "503": {
            "description": "Email is not configured on this server.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PasswordResetRequest"
              }
            }
          }
        }
      }
    },
    "/api/auth/password-reset/confirm": {
      "servers": [
        {
          "url": "https://console.mindthefront.com"
        }
      ],
      "post": {
        "operationId": "resetPassword",
        "summary": "Set a new password",
        "description": "Takes the token from the reset link (the part after `#reset=`). Sets the password, signs out every other session of the account and signs this client in. The machine token does not change.",
        "tags": [
          "auth"
        ],
        "responses": {
          "200": {
            "description": "The account and its business summary; sets the session cookie.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MeResponse"
                }
              }
            }
          },
          "400": {
            "description": "The password fails its constraints, or the link is invalid, used or expired.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Login limit exceeded: 8 per minute per IP by default.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PasswordResetConfirmRequest"
              }
            }
          }
        }
      }
    },
    "/api/businesses/{businessId}/configs": {
      "servers": [
        {
          "url": "https://console.mindthefront.com"
        }
      ],
      "post": {
        "operationId": "createDraft",
        "summary": "Create a draft config",
        "description": "Always creates a new draft with the next version; earlier drafts stay. Validated before it is stored; an invalid document is rejected with 422.",
        "tags": [
          "owner"
        ],
        "responses": {
          "201": {
            "description": "The stored draft: id, version, config, status.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConfigResponse"
                }
              }
            }
          },
          "400": {
            "description": "The body is not JSON, or a required parameter is missing.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "No session and no owner token, or an unknown token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Absent, or not yours.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "The config fails the schema or flow-lint; `errors` lists each failure with `path`, `message` and `rule`.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "businessId",
            "in": "path",
            "required": true,
            "description": "Your business id.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "x-example": "curl -X POST https://console.mindthefront.com/api/businesses/$BUSINESS/configs \\\n  -H \"X-Owner-Token: $TOKEN\" -H 'Content-Type: application/json' --data @config.json",
        "security": [
          {
            "ownerToken": []
          },
          {
            "ownerSession": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ConfigDocument"
              }
            }
          }
        }
      }
    },
    "/api/businesses/{businessId}/configs/generate": {
      "servers": [
        {
          "url": "https://console.mindthefront.com"
        }
      ],
      "post": {
        "operationId": "generateDraft",
        "summary": "Generate a draft from a description",
        "description": "The model assembles a config, the server stamps the time zone onto appointment fields, validates and re-prompts on errors up to the attempt limit. Without `timeZone` the connected calendar's zone is used; with neither the answer is 422.",
        "tags": [
          "owner"
        ],
        "responses": {
          "201": {
            "description": "The stored draft.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConfigResponse"
                }
              }
            }
          },
          "400": {
            "description": "The body is not JSON, or a required parameter is missing.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "No session and no owner token, or an unknown token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "402": {
            "description": "Config generations of the plan are used up. Write the config yourself and save it with `createDraft`: that spends no generations.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Absent, or not yours.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "No valid config after the attempt limit, or an appointment field with no time zone to take.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Generation limit for this business exceeded: 10 per hour by default.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "503": {
            "description": "Generation is not configured on this deployment.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "businessId",
            "in": "path",
            "required": true,
            "description": "Your business id.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "security": [
          {
            "ownerToken": []
          },
          {
            "ownerSession": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GenerateConfigRequest"
              }
            }
          }
        }
      }
    },
    "/api/businesses/{businessId}/configs/draft": {
      "servers": [
        {
          "url": "https://console.mindthefront.com"
        }
      ],
      "post": {
        "operationId": "editableDraft",
        "summary": "Get the editable draft",
        "description": "Returns the existing draft, or forks the published config into a new one. 404 when the business has neither.",
        "tags": [
          "owner"
        ],
        "responses": {
          "200": {
            "description": "The draft to edit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConfigResponse"
                }
              }
            }
          },
          "401": {
            "description": "No session and no owner token, or an unknown token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Neither a draft nor a published config.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "businessId",
            "in": "path",
            "required": true,
            "description": "Your business id.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "security": [
          {
            "ownerToken": []
          },
          {
            "ownerSession": []
          }
        ]
      }
    },
    "/api/configs/{configId}": {
      "servers": [
        {
          "url": "https://console.mindthefront.com"
        }
      ],
      "get": {
        "operationId": "getConfig",
        "summary": "Read a config",
        "description": "Any config of your business, draft or published.",
        "tags": [
          "owner"
        ],
        "responses": {
          "200": {
            "description": "The config.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConfigResponse"
                }
              }
            }
          },
          "401": {
            "description": "No session and no owner token, or an unknown token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not yours, or absent.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "configId",
            "in": "path",
            "required": true,
            "description": "Config id.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "security": [
          {
            "ownerToken": []
          },
          {
            "ownerSession": []
          }
        ]
      },
      "put": {
        "operationId": "updateDraft",
        "summary": "Replace a draft's document",
        "description": "Validated before it is stored, exactly as on creation. Published configs are not editable in place.",
        "tags": [
          "owner"
        ],
        "responses": {
          "200": {
            "description": "The updated draft.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConfigResponse"
                }
              }
            }
          },
          "400": {
            "description": "The body is not JSON, or a required parameter is missing.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "No session and no owner token, or an unknown token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not yours, or absent.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "409": {
            "description": "A published config cannot be edited; create a new draft.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "The config fails the schema or flow-lint; `errors` lists each failure with `path`, `message` and `rule`.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "configId",
            "in": "path",
            "required": true,
            "description": "Draft id.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "security": [
          {
            "ownerToken": []
          },
          {
            "ownerSession": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ConfigDocument"
              }
            }
          }
        }
      }
    },
    "/api/configs/{configId}/publish": {
      "servers": [
        {
          "url": "https://console.mindthefront.com"
        }
      ],
      "post": {
        "operationId": "publishConfig",
        "summary": "Publish a draft",
        "description": "Makes the draft the config served to every widget with this business's key and invalidates the cached copy.",
        "tags": [
          "owner"
        ],
        "responses": {
          "200": {
            "description": "The config, now published.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConfigResponse"
                }
              }
            }
          },
          "401": {
            "description": "No session and no owner token, or an unknown token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not yours, or absent.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "configId",
            "in": "path",
            "required": true,
            "description": "Draft id.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "x-example": "curl -X POST https://console.mindthefront.com/api/configs/$CONFIG/publish -H \"X-Owner-Token: $TOKEN\"",
        "security": [
          {
            "ownerToken": []
          },
          {
            "ownerSession": []
          }
        ]
      }
    },
    "/api/businesses/{businessId}/availability-preview": {
      "servers": [
        {
          "url": "https://console.mindthefront.com"
        }
      ],
      "post": {
        "operationId": "previewAvailability",
        "summary": "Preview free slots for an unsaved config",
        "description": "Slots for a config sent in the body, before it is saved. Same engine and same calendar as the public endpoint.",
        "tags": [
          "owner"
        ],
        "responses": {
          "200": {
            "description": "`slots`, `timeZone`, `durationMinutes`, `sourceUnavailable`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AvailabilityResponse"
                }
              }
            }
          },
          "400": {
            "description": "The body is not JSON, or a required parameter is missing.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "No session and no owner token, or an unknown token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "No such field in the submitted config.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "businessId",
            "in": "path",
            "required": true,
            "description": "Your business id.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "field",
            "in": "query",
            "required": true,
            "description": "Name of the appointment field in the submitted config.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "security": [
          {
            "ownerToken": []
          },
          {
            "ownerSession": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ConfigDocument"
              }
            }
          }
        }
      }
    },
    "/api/businesses/{businessId}/submissions": {
      "servers": [
        {
          "url": "https://console.mindthefront.com"
        }
      ],
      "get": {
        "operationId": "listSubmissions",
        "summary": "List the requests of a business",
        "description": "Newest first, with values and statuses.",
        "tags": [
          "owner"
        ],
        "responses": {
          "200": {
            "description": "The requests.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/SubmissionResponse"
                  }
                }
              }
            }
          },
          "401": {
            "description": "No session and no owner token, or an unknown token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Absent, or not yours.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "businessId",
            "in": "path",
            "required": true,
            "description": "Your business id.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "security": [
          {
            "ownerToken": []
          },
          {
            "ownerSession": []
          }
        ]
      }
    },
    "/api/submissions/{submissionId}/status": {
      "servers": [
        {
          "url": "https://console.mindthefront.com"
        }
      ],
      "patch": {
        "operationId": "changeSubmissionStatus",
        "summary": "Confirm or decline a request",
        "description": "The visitor sees the new status on the page they came back to.",
        "tags": [
          "owner"
        ],
        "responses": {
          "200": {
            "description": "The request with its new status.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SubmissionResponse"
                }
              }
            }
          },
          "400": {
            "description": "A required header, parameter or field is missing or fails its constraints.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "No session and no owner token, or an unknown token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not yours, or absent.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "description": "Unknown status.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "submissionId",
            "in": "path",
            "required": true,
            "description": "Request id.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "security": [
          {
            "ownerToken": []
          },
          {
            "ownerSession": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ChangeStatusRequest"
              }
            }
          }
        }
      }
    },
    "/api/businesses/{businessId}/telegram": {
      "servers": [
        {
          "url": "https://console.mindthefront.com"
        }
      ],
      "put": {
        "operationId": "setTelegramChat",
        "summary": "Set the Telegram chat for notifications",
        "description": "`chatId: null` stops notifications. The notification chat lives here, not in the config's `integrations`.",
        "tags": [
          "owner"
        ],
        "responses": {
          "200": {
            "description": "The business summary.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BusinessSummary"
                }
              }
            }
          },
          "400": {
            "description": "A required header, parameter or field is missing or fails its constraints.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "No session and no owner token, or an unknown token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Absent, or not yours.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "businessId",
            "in": "path",
            "required": true,
            "description": "Your business id.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "security": [
          {
            "ownerToken": []
          },
          {
            "ownerSession": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateTelegramRequest"
              }
            }
          }
        }
      }
    },
    "/api/businesses/{businessId}/calendar": {
      "servers": [
        {
          "url": "https://console.mindthefront.com"
        }
      ],
      "get": {
        "operationId": "calendarStatus",
        "summary": "Read the calendar connection",
        "description": "Whether a calendar is connected, which one, its time zone, and whether the connection needs re-authorising.",
        "tags": [
          "calendar"
        ],
        "responses": {
          "200": {
            "description": "Connection state.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnectionResponse"
                }
              }
            }
          },
          "401": {
            "description": "No session and no owner token, or an unknown token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Absent, or not yours.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "businessId",
            "in": "path",
            "required": true,
            "description": "Your business id.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "security": [
          {
            "ownerToken": []
          },
          {
            "ownerSession": []
          }
        ]
      }
    },
    "/api/businesses/{businessId}/calendar/authorize": {
      "servers": [
        {
          "url": "https://console.mindthefront.com"
        }
      ],
      "post": {
        "operationId": "startCalendarAuthorization",
        "summary": "Start connecting a calendar",
        "description": "Returns the consent URL; a person opens it in a browser.",
        "tags": [
          "calendar"
        ],
        "responses": {
          "200": {
            "description": "`{ \"authorizationUrl\": \"...\" }`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthorizationResponse"
                }
              }
            }
          },
          "401": {
            "description": "No session and no owner token, or an unknown token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Absent, or not yours.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "503": {
            "description": "Calendar integration is not configured on this deployment.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "businessId",
            "in": "path",
            "required": true,
            "description": "Your business id.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "security": [
          {
            "ownerToken": []
          },
          {
            "ownerSession": []
          }
        ]
      }
    },
    "/api/businesses/{businessId}/calendar/calendars": {
      "servers": [
        {
          "url": "https://console.mindthefront.com"
        }
      ],
      "get": {
        "operationId": "listCalendars",
        "summary": "List the calendars of the connected account",
        "description": "What the owner picks from after consent.",
        "tags": [
          "calendar"
        ],
        "responses": {
          "200": {
            "description": "Calendars with ids, names and time zones.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/CalendarSummary"
                  }
                }
              }
            }
          },
          "401": {
            "description": "No session and no owner token, or an unknown token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "No connection to list from.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "businessId",
            "in": "path",
            "required": true,
            "description": "Your business id.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "security": [
          {
            "ownerToken": []
          },
          {
            "ownerSession": []
          }
        ]
      }
    },
    "/api/businesses/{businessId}/calendar/calendar": {
      "servers": [
        {
          "url": "https://console.mindthefront.com"
        }
      ],
      "post": {
        "operationId": "selectCalendar",
        "summary": "Choose the calendar to read busy time from",
        "description": "Its time zone is used by generation when the request names none.",
        "tags": [
          "calendar"
        ],
        "responses": {
          "200": {
            "description": "Connection state after the choice.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConnectionResponse"
                }
              }
            }
          },
          "400": {
            "description": "A required header, parameter or field is missing or fails its constraints.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "401": {
            "description": "No session and no owner token, or an unknown token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "No such calendar in the connected account.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "businessId",
            "in": "path",
            "required": true,
            "description": "Your business id.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "security": [
          {
            "ownerToken": []
          },
          {
            "ownerSession": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SelectCalendarRequest"
              }
            }
          }
        }
      }
    },
    "/api/businesses/{businessId}/calendar/disconnect": {
      "servers": [
        {
          "url": "https://console.mindthefront.com"
        }
      ],
      "post": {
        "operationId": "disconnectCalendar",
        "summary": "Disconnect the calendar",
        "description": "Busy time stops narrowing the schedule; slots keep being offered from the declared hours with the usual caveat.",
        "tags": [
          "calendar"
        ],
        "responses": {
          "204": {
            "description": "Disconnected."
          },
          "401": {
            "description": "No session and no owner token, or an unknown token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Absent, or not yours.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "businessId",
            "in": "path",
            "required": true,
            "description": "Your business id.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "security": [
          {
            "ownerToken": []
          },
          {
            "ownerSession": []
          }
        ]
      }
    },
    "/api/public/configs/validate": {
      "servers": [
        {
          "url": "https://mindthefront.com"
        }
      ],
      "post": {
        "operationId": "validateConfig",
        "summary": "Validate a config without storing it",
        "description": "Runs the checks a save runs: JSON Schema first, then flow-lint. Nothing is stored and no key is needed. Status is 200 whether or not the document is valid. Each error carries `path`, `message` and `rule` (`schema` or `flow-lint`).",
        "tags": [
          "public"
        ],
        "responses": {
          "200": {
            "description": "`{ \"valid\": true, \"errors\": [] }`, or `valid: false` with one entry per problem.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationResult"
                }
              }
            }
          },
          "400": {
            "description": "The body is not JSON, or a required parameter is missing.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "413": {
            "description": "Body over 64 KB.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Per-IP write limit exceeded: 10 requests per minute by default.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "x-example": "curl -X POST https://mindthefront.com/api/public/configs/validate \\\n  -H 'Content-Type: application/json' --data @config.json",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ConfigDocument"
              }
            }
          }
        }
      }
    },
    "/api/public/config-schema": {
      "servers": [
        {
          "url": "https://mindthefront.com"
        }
      ],
      "get": {
        "operationId": "getConfigSchema",
        "summary": "Fetch the canonical config JSON Schema",
        "description": "The exact file the server validates against, not a copy of it. ETagged and publicly cacheable.",
        "tags": [
          "public"
        ],
        "parameters": [
          {
            "name": "If-None-Match",
            "in": "header",
            "required": false,
            "description": "Conditional request; a matching ETag returns 304.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "JSON Schema 2020-12 document.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JsonSchemaDocument"
                }
              }
            }
          },
          "304": {
            "description": "Not modified."
          }
        },
        "x-example": "curl https://mindthefront.com/api/public/config-schema",
        "security": []
      }
    },
    "/api/businesses/current": {
      "servers": [
        {
          "url": "https://console.mindthefront.com"
        }
      ],
      "get": {
        "operationId": "currentBusiness",
        "summary": "Identify the business behind a credential",
        "description": "What the machine token manages: business id, name and publishable key. The response carries no token and no account credentials.",
        "tags": [
          "owner"
        ],
        "responses": {
          "200": {
            "description": "Business id, name and publishable key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BusinessSummary"
                }
              }
            }
          },
          "401": {
            "description": "No session and no owner token, or an unknown token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "x-example": "curl https://console.mindthefront.com/api/businesses/current -H \"X-Owner-Token: $TOKEN\"",
        "security": [
          {
            "ownerToken": []
          },
          {
            "ownerSession": []
          }
        ]
      }
    },
    "/api/businesses/{businessId}/plan": {
      "servers": [
        {
          "url": "https://console.mindthefront.com"
        }
      ],
      "get": {
        "operationId": "getPlan",
        "summary": "Plan and usage of a business",
        "description": "The plan in force, its dates, usage in the current windows against the limits, prepaid requests and whether the widget shows — with the reason when it does not.",
        "tags": [
          "owner"
        ],
        "parameters": [
          {
            "name": "businessId",
            "in": "path",
            "required": true,
            "description": "Your business id.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The plan and usage.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlanResponse"
                }
              }
            }
          },
          "401": {
            "description": "No session and no owner token, or an unknown token.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Absent, or not yours.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "security": [
          {
            "ownerToken": []
          },
          {
            "ownerSession": []
          }
        ]
      }
    },
    "/api/businesses/{businessId}/owner-token": {
      "servers": [
        {
          "url": "https://console.mindthefront.com"
        }
      ],
      "get": {
        "operationId": "revealOwnerToken",
        "summary": "Reveal the machine token",
        "description": "Session only; a request authenticated with the token itself gets 401.",
        "tags": [
          "owner"
        ],
        "parameters": [
          {
            "name": "businessId",
            "in": "path",
            "required": true,
            "description": "Your business id.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "`{ \"ownerToken\": \"ot_...\" }`",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OwnerTokenResponse"
                }
              }
            }
          },
          "401": {
            "description": "No owner session.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not your business.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "security": [
          {
            "ownerSession": []
          }
        ]
      }
    },
    "/api/businesses/{businessId}/owner-token/rotate": {
      "servers": [
        {
          "url": "https://console.mindthefront.com"
        }
      ],
      "post": {
        "operationId": "rotateOwnerToken",
        "summary": "Issue a new machine token",
        "description": "The previous token stops working immediately. The publishable key and the published config are unchanged.",
        "tags": [
          "owner"
        ],
        "parameters": [
          {
            "name": "businessId",
            "in": "path",
            "required": true,
            "description": "Your business id.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "`{ \"ownerToken\": \"ot_...\" }` — the new token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OwnerTokenResponse"
                }
              }
            }
          },
          "401": {
            "description": "No owner session.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "404": {
            "description": "Not your business.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        },
        "security": [
          {
            "ownerSession": []
          }
        ]
      }
    }
  },
  "components": {
    "schemas": {
      "Problem": {
        "type": "object",
        "additionalProperties": true,
        "required": [
          "status",
          "detail"
        ],
        "properties": {
          "type": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "type": "integer"
          },
          "detail": {
            "type": "string"
          },
          "instance": {
            "type": "string"
          },
          "errors": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "path",
                "message"
              ],
              "properties": {
                "path": {
                  "type": "string"
                },
                "message": {
                  "type": "string"
                },
                "rule": {
                  "enum": [
                    "schema",
                    "flow-lint"
                  ],
                  "description": "Which check rejected a config: the same checks and values as validateConfig."
                }
              }
            }
          },
          "field": {
            "type": "string"
          }
        },
        "description": "Error body of every 4xx and 5xx response. `errors` lists validation failures (422): for a config each carries `path` (JSON Pointer), `message` and `rule`. `field` names the conflicting field (409)."
      },
      "ConfigDocument": {
        "type": "object",
        "description": "A process config. Its schema is served at `GET /api/public/config-schema`."
      },
      "JsonSchemaDocument": {
        "type": "object",
        "description": "A JSON Schema 2020-12 document."
      },
      "ConfigResponse": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "id",
          "businessId",
          "version",
          "status",
          "config",
          "createdAt",
          "updatedAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "businessId": {
            "type": "string",
            "format": "uuid"
          },
          "version": {
            "type": "integer",
            "minimum": 1
          },
          "status": {
            "enum": [
              "draft",
              "published"
            ]
          },
          "config": {
            "$ref": "#/components/schemas/ConfigDocument"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "GenerateConfigRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "description"
        ],
        "properties": {
          "description": {
            "type": "string",
            "minLength": 1
          },
          "timeZone": {
            "type": "string",
            "description": "IANA zone for appointment fields; without it the connected calendar's zone is used."
          }
        }
      },
      "AvailabilityResponse": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "slots",
          "timeZone",
          "durationMinutes",
          "sourceUnavailable"
        ],
        "properties": {
          "slots": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "date-time"
            }
          },
          "timeZone": {
            "type": [
              "string",
              "null"
            ]
          },
          "durationMinutes": {
            "type": "integer"
          },
          "sourceUnavailable": {
            "type": "boolean",
            "description": "true when busy time could not be read: slots come from the schedule alone."
          }
        }
      },
      "CreateConversationRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "publishableKey"
        ],
        "properties": {
          "publishableKey": {
            "type": "string",
            "minLength": 1
          }
        }
      },
      "ConversationResponse": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "visitorToken"
        ],
        "properties": {
          "visitorToken": {
            "type": "string"
          }
        }
      },
      "SubmitRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "visitorToken",
          "fields"
        ],
        "properties": {
          "visitorToken": {
            "type": "string",
            "minLength": 1
          },
          "fields": {
            "type": "object",
            "description": "Values keyed by field name, validated against the published config's submission schema."
          }
        }
      },
      "SubmissionStatus": {
        "enum": [
          "new",
          "confirmed",
          "rejected"
        ]
      },
      "SubmissionResponse": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "id",
          "status",
          "fields",
          "createdAt",
          "updatedAt",
          "erasedAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "$ref": "#/components/schemas/SubmissionStatus"
          },
          "fields": {
            "type": "object"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "erasedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When the free plan erased the fields: only the latest requests keep them. `fields` is then empty."
          }
        }
      },
      "ChangeStatusRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "status"
        ],
        "properties": {
          "status": {
            "$ref": "#/components/schemas/SubmissionStatus"
          }
        }
      },
      "UpdateTelegramRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [],
        "properties": {
          "chatId": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 100
          }
        }
      },
      "BusinessSummary": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "id",
          "name",
          "publishableKey",
          "telegramChatId"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "publishableKey": {
            "type": "string"
          },
          "telegramChatId": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "RegisterRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "email",
          "password",
          "businessName"
        ],
        "properties": {
          "email": {
            "type": "string",
            "format": "email",
            "maxLength": 254
          },
          "password": {
            "type": "string",
            "minLength": 8,
            "maxLength": 128
          },
          "businessName": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
          },
          "agent": {
            "type": "boolean",
            "default": false,
            "description": "The sign-up started from the agent link: the confirmation link opens the API access screen."
          }
        }
      },
      "ConfirmRegistrationRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "token"
        ],
        "properties": {
          "token": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100,
            "description": "The part of the confirmation link after `#verify=`."
          }
        }
      },
      "LoginRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "email",
          "password"
        ],
        "properties": {
          "email": {
            "type": "string",
            "format": "email",
            "maxLength": 254
          },
          "password": {
            "type": "string",
            "minLength": 1,
            "maxLength": 128
          }
        }
      },
      "DeleteAccountRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "email"
        ],
        "properties": {
          "email": {
            "type": "string",
            "minLength": 1,
            "maxLength": 254,
            "description": "The email of the account being deleted — a confirmation."
          }
        }
      },
      "PasswordResetRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "email"
        ],
        "properties": {
          "email": {
            "type": "string",
            "format": "email",
            "maxLength": 254
          }
        }
      },
      "PasswordResetConfirmRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "token",
          "password"
        ],
        "properties": {
          "token": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100,
            "description": "The part of the reset link after `#reset=`."
          },
          "password": {
            "type": "string",
            "minLength": 8,
            "maxLength": 128
          }
        }
      },
      "MeResponse": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "accountId",
          "email",
          "business"
        ],
        "properties": {
          "accountId": {
            "type": "string",
            "format": "uuid"
          },
          "email": {
            "type": "string"
          },
          "business": {
            "$ref": "#/components/schemas/BusinessSummary"
          }
        }
      },
      "OwnerTokenResponse": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "ownerToken"
        ],
        "properties": {
          "ownerToken": {
            "type": "string",
            "description": "Value for the X-Owner-Token header."
          }
        }
      },
      "ShowcaseEntry": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "publishableKey",
          "businessName",
          "prompt"
        ],
        "properties": {
          "publishableKey": {
            "type": "string"
          },
          "businessName": {
            "type": "string"
          },
          "prompt": {
            "type": "string"
          }
        }
      },
      "ValidationResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "valid",
          "errors"
        ],
        "properties": {
          "valid": {
            "type": "boolean"
          },
          "errors": {
            "type": "array",
            "items": {
              "type": "object",
              "additionalProperties": false,
              "required": [
                "path",
                "message",
                "rule"
              ],
              "properties": {
                "path": {
                  "type": "string",
                  "description": "JSON Pointer to the node that breaks the rule."
                },
                "message": {
                  "type": "string"
                },
                "rule": {
                  "enum": [
                    "schema",
                    "flow-lint"
                  ]
                }
              }
            }
          }
        }
      },
      "SelectCalendarRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "calendarId"
        ],
        "properties": {
          "calendarId": {
            "type": "string",
            "minLength": 1,
            "maxLength": 300
          }
        }
      },
      "AuthorizationResponse": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "authorizationUrl"
        ],
        "properties": {
          "authorizationUrl": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "ConnectionResponse": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "configured",
          "connected",
          "status",
          "calendarId",
          "calendarSummary",
          "timeZone"
        ],
        "properties": {
          "configured": {
            "type": "boolean"
          },
          "connected": {
            "type": "boolean"
          },
          "status": {
            "type": [
              "string",
              "null"
            ]
          },
          "calendarId": {
            "type": [
              "string",
              "null"
            ]
          },
          "calendarSummary": {
            "type": [
              "string",
              "null"
            ]
          },
          "timeZone": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "CalendarSummary": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "id",
          "summary",
          "timeZone",
          "primary"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "summary": {
            "type": "string"
          },
          "timeZone": {
            "type": [
              "string",
              "null"
            ]
          },
          "primary": {
            "type": "boolean"
          }
        }
      },
      "UsageCounter": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "used",
          "limit",
          "window"
        ],
        "properties": {
          "used": {
            "type": "integer",
            "minimum": 0
          },
          "limit": {
            "type": [
              "integer",
              "null"
            ],
            "description": "null — no plan limit."
          },
          "window": {
            "enum": [
              "day",
              "month",
              "lifetime"
            ],
            "description": "`day` — the last 24 hours; `month` — the calendar month in UTC; `lifetime` — all time."
          }
        }
      },
      "PlanResponse": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "plan",
          "trial",
          "assignedPlan",
          "paidUntil",
          "trialEndsAt",
          "submissions",
          "packBalance",
          "generations",
          "widget"
        ],
        "properties": {
          "plan": {
            "enum": [
              "free",
              "basic",
              "premium",
              "internal"
            ],
            "description": "The plan in force now."
          },
          "trial": {
            "type": "boolean",
            "description": "The plan in force is the Basic trial."
          },
          "assignedPlan": {
            "enum": [
              "free",
              "basic",
              "premium",
              "internal"
            ],
            "description": "The plan set for the business; it lapses to free when paid time and the trial run out."
          },
          "paidUntil": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "trialEndsAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "submissions": {
            "$ref": "#/components/schemas/UsageCounter"
          },
          "packBalance": {
            "type": "integer",
            "minimum": 0,
            "description": "Prepaid requests left beyond the Basic monthly quota."
          },
          "generations": {
            "$ref": "#/components/schemas/UsageCounter"
          },
          "widget": {
            "type": "object",
            "additionalProperties": false,
            "required": [
              "available",
              "reason"
            ],
            "properties": {
              "available": {
                "type": "boolean"
              },
              "reason": {
                "enum": [
                  "daily_limit",
                  "quota_exhausted",
                  null
                ],
                "description": "Why the widget does not show; null when it does."
              }
            }
          }
        }
      }
    },
    "securitySchemes": {
      "ownerToken": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Owner-Token",
        "description": "Machine token of one business, issued in the console."
      },
      "ownerSession": {
        "type": "apiKey",
        "in": "cookie",
        "name": "gc_session",
        "description": "Owner session set by login or register."
      },
      "visitorToken": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Visitor-Token",
        "description": "Issued by createConversation; reads one visitor's request."
      }
    }
  }
}
