{
  "openapi": "3.1.0",
  "info": {
    "title": "The Crooked Swan API",
    "version": "1.0.0",
    "summary": "Public endpoints for The Crooked Swan, a Georgian pub and hotel in Crewkerne, Somerset.",
    "description": "The Crooked Swan is a pub, restaurant and 8-bedroom hotel at 4 Church Street, Crewkerne, Somerset TA18 7HR. This API has one operation: signing a person up to the pub's newsletter of news and offers. Only call it when the person has asked to be signed up; it is a marketing consent.\n\nThings this API does not do: room bookings are made at https://direct-book.com/properties/thecrookedswanhoteldirect, and table bookings by email to info@crookedswan.com or by phone on +44 1460 208414. Opening times, menus, rooms and events are described in https://crookedswan.com/llms.txt.\n\nErrors are RFC 9457 problem details (application/problem+json) with a stable `code` and a `hint` saying how to fix the request.",
    "contact": {
      "name": "The Crooked Swan",
      "email": "info@crookedswan.com",
      "url": "https://crookedswan.com/contact/"
    }
  },
  "externalDocs": {
    "description": "Site summary for agents (llms.txt)",
    "url": "https://crookedswan.com/llms.txt"
  },
  "servers": [
    {
      "url": "https://crookedswan.com"
    }
  ],
  "tags": [
    {
      "name": "newsletter",
      "description": "News and offers from The Crooked Swan."
    }
  ],
  "paths": {
    "/api/subscribe": {
      "post": {
        "operationId": "subscribeToNewsletter",
        "tags": [
          "newsletter"
        ],
        "summary": "Sign a person up to The Crooked Swan newsletter",
        "description": "Adds a person to the newsletter list for news and offers from The Crooked Swan. Only call this with the person's explicit consent to receive marketing email. Send `Accept: application/json` to get a JSON reply; without it a plain form post gets a short HTML page. Repeat sign-ups are accepted and stored again. See https://crookedswan.com/privacy/ for how the data is used. No authentication is needed.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Subscription"
              }
            },
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/Subscription"
              }
            },
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/Subscription"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The person is on the list.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Success"
                },
                "example": {
                  "ok": true,
                  "message": "Thanks, you're on the list."
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "413": {
            "$ref": "#/components/responses/TooLarge"
          },
          "502": {
            "$ref": "#/components/responses/StorageUnavailable"
          }
        },
        "security": []
      }
    }
  },
  "components": {
    "schemas": {
      "Subscription": {
        "type": "object",
        "description": "The person to sign up. Strings only; each field is trimmed and capped at 500 characters, with at most 20 fields and 4 KB in total.",
        "required": [
          "email"
        ],
        "properties": {
          "email": {
            "type": "string",
            "format": "email",
            "maxLength": 500,
            "description": "The person's email address.",
            "examples": [
              "jane@example.com"
            ]
          },
          "first_name": {
            "type": "string",
            "maxLength": 500,
            "description": "Given name."
          },
          "last_name": {
            "type": "string",
            "maxLength": 500,
            "description": "Family name."
          },
          "phone": {
            "type": "string",
            "maxLength": 500,
            "description": "Phone number, any format."
          }
        }
      },
      "Success": {
        "type": "object",
        "required": [
          "ok",
          "message"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          },
          "message": {
            "type": "string",
            "description": "A short confirmation to show the person."
          }
        }
      },
      "Problem": {
        "type": "object",
        "description": "RFC 9457 problem details, plus `code`, `hint`, `ok` and `message`.",
        "required": [
          "type",
          "title",
          "status",
          "detail",
          "code",
          "hint",
          "ok",
          "message"
        ],
        "properties": {
          "type": {
            "type": "string",
            "format": "uri",
            "description": "Identifies the problem type."
          },
          "title": {
            "type": "string",
            "description": "Short summary of the HTTP status."
          },
          "status": {
            "type": "integer",
            "description": "The HTTP status code."
          },
          "detail": {
            "type": "string",
            "description": "What went wrong, in words fit to show a person."
          },
          "code": {
            "type": "string",
            "enum": [
              "invalid_body",
              "invalid_email",
              "too_large",
              "storage_unavailable",
              "method_not_allowed",
              "not_found"
            ],
            "description": "Stable machine-readable error code."
          },
          "hint": {
            "type": "string",
            "description": "How to fix the request."
          },
          "ok": {
            "type": "boolean",
            "const": false
          },
          "message": {
            "type": "string",
            "description": "Same as detail."
          }
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "The body could not be read (code `invalid_body`) or the email is not valid (code `invalid_email`).",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            },
            "example": {
              "type": "https://crookedswan.com/openapi.json#error-invalid_email",
              "title": "Bad Request",
              "status": 400,
              "detail": "That email address does not look right. Please check it and try again.",
              "code": "invalid_email",
              "hint": "Provide a valid email address in the email field.",
              "ok": false,
              "message": "That email address does not look right. Please check it and try again."
            }
          }
        }
      },
      "TooLarge": {
        "description": "Too many fields or too much data (code `too_large`).",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      },
      "StorageUnavailable": {
        "description": "The sign-up could not be stored (code `storage_unavailable`). Retry later or email info@crookedswan.com.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      }
    }
  }
}
