#1 [internal] load local bake definitions
#1 reading from stdin 653B done
#1 DONE 0.0s

#2 [internal] load build definition from Dockerfile
#2 transferring dockerfile: 1.13kB 0.0s done
#2 DONE 0.1s

#3 [internal] load metadata for docker.io/library/python:3.12-slim
#3 DONE 1.8s

#4 [internal] load metadata for ghcr.io/astral-sh/uv:latest
#4 DONE 1.8s

#5 [internal] load .dockerignore
#5 transferring context: 2B done
#5 DONE 0.0s

#6 [internal] load build context
#6 transferring context: 771.25kB 0.1s done
#6 DONE 0.1s

#7 FROM ghcr.io/astral-sh/uv:latest@sha256:e85be844203885286c60ffad8a858d48afb6c5a5c237ca0e67f12e74b8f174b1
#7 resolve ghcr.io/astral-sh/uv:latest@sha256:e85be844203885286c60ffad8a858d48afb6c5a5c237ca0e67f12e74b8f174b1 0.1s done
#7 DONE 0.1s

#8 [base 1/6] FROM docker.io/library/python:3.12-slim@sha256:2c941e860699f878900b0edc2403613c234d4b32eda3cc9fa7036991a2a63c4a
#8 resolve docker.io/library/python:3.12-slim@sha256:2c941e860699f878900b0edc2403613c234d4b32eda3cc9fa7036991a2a63c4a 0.1s done
#8 DONE 0.1s

#9 [base 2/6] COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
#9 CACHED

#10 [base 3/6] WORKDIR /app
#10 CACHED

#11 [base 4/6] COPY pyproject.toml uv.lock* ./
#11 DONE 0.1s

#12 [base 5/6] RUN uv sync --frozen --no-install-project 2>/dev/null || uv sync --no-install-project
#12 78.77 Resolved 82 packages in 1m 17s
#12 81.70 Downloading pydantic-core (2.0MiB)
#12 81.70 Downloading pygments (1.2MiB)
#12 81.75 Downloading pip (1.7MiB)
#12 81.83 Downloading cryptography (4.5MiB)
#12 81.86 Downloading asyncpg (3.4MiB)
#12 81.86 Downloading sqlalchemy (3.2MiB)
#12 81.87 Downloading uvloop (4.2MiB)
#12 81.88 Downloading ast-serialize (1.2MiB)
#12 81.88 Downloading ruff (10.5MiB)
#12 81.88 Downloading mypy (14.6MiB)
#12 81.90 Downloading phonenumbers (2.5MiB)
#12 287.7  Downloaded ast-serialize
#12 288.1  Downloaded pygments
#12 328.8  Downloaded pip
#12 361.9  Downloaded pydantic-core
#12 406.1  Downloaded phonenumbers
#12 481.4  Downloaded sqlalchemy
#12 494.3  Downloaded asyncpg
#12 544.0  Downloaded uvloop
#12 558.4  Downloaded cryptography
#12 813.3  Downloaded ruff
#12 880.8  Downloaded mypy
#12 880.8 Prepared 80 packages in 13m 22s
#12 887.2 Installed 80 packages in 6.39s
#12 901.6 Bytecode compiled 3794 files in 14.38s
#12 901.6  + alembic==1.19.1
#12 901.6  + annotated-doc==0.0.5
#12 901.6  + annotated-types==0.8.0
#12 901.6  + anyio==4.14.2
#12 901.6  + apscheduler==3.11.3
#12 901.6  + ast-serialize==0.8.0
#12 901.6  + asyncpg==0.31.0
#12 901.6  + boolean-py==5.0
#12 901.6  + cachecontrol==0.14.4
#12 901.6  + certifi==2026.7.22
#12 901.6  + cffi==2.1.1
#12 901.6  + charset-normalizer==3.5.1
#12 901.6  + click==8.4.2
#12 901.6  + cryptography==50.0.0
#12 901.6  + cyclonedx-python-lib==11.12.0
#12 901.6  + defusedxml==0.7.1
#12 901.6  + dnspython==2.8.0
#12 901.6  + email-validator==2.3.0
#12 901.6  + fastapi==0.141.1
#12 901.6  + filelock==3.32.3
#12 901.6  + google-auth==2.56.3
#12 901.6  + greenlet==3.5.5
#12 901.6  + h11==0.16.0
#12 901.6  + httpcore==1.0.9
#12 901.6  + httptools==0.8.0
#12 901.6  + httpx==0.28.1
#12 901.6  + idna==3.19
#12 901.6  + iniconfig==2.3.0
#12 901.6  + itsdangerous==2.2.0
#12 901.6  + librt==0.15.0
#12 901.6  + license-expression==30.4.4
#12 901.6  + mako==1.4.1
#12 901.6  + markdown-it-py==4.2.0
#12 901.6  + markupsafe==3.0.3
#12 901.6  + mdurl==0.1.2
#12 901.6  + msgpack==1.2.1
#12 901.6  + mypy==2.3.1
#12 901.6  + mypy-extensions==1.1.0
#12 901.6  + packageurl-python==0.17.6
#12 901.6  + packaging==26.3
#12 901.6  + pathspec==1.1.1
#12 901.6  + phonenumbers==9.0.37
#12 901.6  + pip==26.2.1
#12 901.6  + pip-api==0.0.34
#12 901.6  + pip-audit==2.10.1
#12 901.6  + pip-requirements-parser==32.0.1
#12 901.6  + platformdirs==4.11.3
#12 901.6  + pluggy==1.6.0
#12 901.6  + py-serializable==2.1.0
#12 901.6  + pyasn1==0.6.4
#12 901.6  + pyasn1-modules==0.4.2
#12 901.6  + pycparser==3.0
#12 901.6  + pydantic==2.13.4
#12 901.6  + pydantic-core==2.46.4
#12 901.6  + pydantic-settings==2.15.0
#12 901.6  + pygments==2.21.0
#12 901.6  + pyparsing==3.3.2
#12 901.6  + pytest==9.1.1
#12 901.6  + pytest-asyncio==1.4.0
#12 901.6  + python-dotenv==1.2.3
#12 901.6  + python-multipart==0.0.32
#12 901.6  + pyyaml==6.0.3
#12 901.6  + requests==2.34.2
#12 901.6  + rich==15.0.0
#12 901.6  + ruff==0.16.3
#12 901.6  + segno==1.6.6
#12 901.6  + sortedcontainers==2.4.0
#12 901.6  + sqlalchemy==2.0.52
#12 901.6  + starlette==1.6.0
#12 901.6  + tomli==2.4.1
#12 901.6  + tomli-w==1.2.0
#12 901.6  + typing-extensions==4.16.0
#12 901.6  + typing-inspection==0.4.4
#12 901.6  + tzdata==2026.3
#12 901.6  + tzlocal==5.4.4
#12 901.6  + urllib3==2.7.0
#12 901.6  + uvicorn==0.52.3
#12 901.6  + uvloop==0.22.1
#12 901.6  + watchfiles==1.2.0
#12 901.6  + websockets==17.0.1
#12 DONE 903.2s

#13 [base 6/6] COPY . .
#13 DONE 0.8s

#14 exporting to image
#14 exporting layers
#14 exporting layers 61.2s done
#14 exporting manifest sha256:079e9cbf382dc293c86bfae980352db830344f192087021fba7730bdc4b71d19 0.1s done
#14 exporting config sha256:8bb1692c9b678e25ca13da127dd5a719c46b55e019d1beb710a2c499f37eb14a 0.1s done
#14 exporting attestation manifest sha256:4a42cbb5127e66182ec2d79617a9441428076e92c51d71a7d50ce2a4cd435adf 0.1s done
#14 exporting manifest list sha256:78861b8cf24047b58ade66866e94054b8e6e301e8b4c906b23abcd038608d8cd 0.0s done
#14 naming to docker.io/library/changeadd-event-host-contact-api:latest done
#14 unpacking to docker.io/library/changeadd-event-host-contact-api:latest
#14 unpacking to docker.io/library/changeadd-event-host-contact-api:latest 22.8s done
#14 DONE 84.4s

#15 resolving provenance for metadata file
#15 DONE 0.3s
{
  "components": {
    "schemas": {
      "AdminEventRead": {
        "description": "Named distinctly from the public `EventRead` on purpose.\n\nTwo Pydantic models sharing a class name make FastAPI fully-qualify *both* in the\nOpenAPI document \u2014 `app__schemas__invitation__EventRead` \u2014 which silently renames the\ngenerated TypeScript type the public invitation pages were already built against.",
        "properties": {
          "capacity": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Capacity"
          },
          "dress_code": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Dress Code"
          },
          "ends_at": {
            "anyOf": [
              {
                "format": "date-time",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Ends At"
          },
          "host_name_1": {
            "title": "Host Name 1",
            "type": "string"
          },
          "host_name_2": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Host Name 2"
          },
          "host_phone": {
            "title": "Host Phone",
            "type": "string"
          },
          "id": {
            "format": "uuid",
            "title": "Id",
            "type": "string"
          },
          "invitation_messages": {
            "additionalProperties": {
              "additionalProperties": {
                "type": "string"
              },
              "type": "object"
            },
            "title": "Invitation Messages",
            "type": "object"
          },
          "is_published": {
            "title": "Is Published",
            "type": "boolean"
          },
          "map_url": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Map Url"
          },
          "notes": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Notes"
          },
          "rsvp_deadline": {
            "anyOf": [
              {
                "format": "date-time",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Rsvp Deadline"
          },
          "slug": {
            "title": "Slug",
            "type": "string"
          },
          "starts_at": {
            "format": "date-time",
            "title": "Starts At",
            "type": "string"
          },
          "theme_key": {
            "title": "Theme Key",
            "type": "string"
          },
          "title_bn": {
            "title": "Title Bn",
            "type": "string"
          },
          "title_en": {
            "title": "Title En",
            "type": "string"
          },
          "type": {
            "$ref": "#/components/schemas/EventType"
          },
          "venue_address": {
            "title": "Venue Address",
            "type": "string"
          },
          "venue_name": {
            "title": "Venue Name",
            "type": "string"
          }
        },
        "required": [
          "id",
          "type",
          "slug",
          "title_en",
          "title_bn",
          "starts_at",
          "ends_at",
          "venue_name",
          "venue_address",
          "map_url",
          "dress_code",
          "notes",
          "host_name_1",
          "host_name_2",
          "host_phone",
          "rsvp_deadline",
          "capacity",
          "is_published",
          "theme_key",
          "invitation_messages"
        ],
        "title": "AdminEventRead",
        "type": "object"
      },
      "AdminPreviewRead": {
        "description": "The link preview as this one guest will see it, for the compose panel (design D5).\n\nA separate model from the guest routes' `LinkPreviewRead` rather than that one with a\nname bolted on. `LinkPreviewRead` exists to be guest-free \u2014 it is what ends up in meta\ntags a crawler caches \u2014 and a nullable `guest_name` on it would be one careless\nassignment away from putting a guest's name there. Two types make the mistake\nunrepresentable instead of merely discouraged (design D3).",
        "properties": {
          "description": {
            "title": "Description",
            "type": "string"
          },
          "guest_name": {
            "title": "Guest Name",
            "type": "string"
          },
          "has_image": {
            "title": "Has Image",
            "type": "boolean"
          },
          "image_url": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Image Url"
          },
          "title": {
            "title": "Title",
            "type": "string"
          }
        },
        "required": [
          "title",
          "description",
          "guest_name",
          "image_url",
          "has_image"
        ],
        "title": "AdminPreviewRead",
        "type": "object"
      },
      "AdminRole": {
        "description": "Two roles (add-admin-access-control design D5).\n\n`co_host` and `viewer` are gone. A Host does everything an event owner needs, but only\ninside the events they own \u2014 the second half of that sentence is `services/scope.py`,\nnot this enum. Role says *what kind of thing*; scope says *to which records*.",
        "enum": [
          "super_admin",
          "host"
        ],
        "title": "AdminRole",
        "type": "string"
      },
      "AdminStatus": {
        "description": "Three states, because \"cannot sign in\" has two very different meanings (design D4).\n\nPENDING is only ever reached by a first Google sign-in: the account exists, nobody has\napproved it, and it appears in the roster's approval queue. WITHDRAWN is a decision\nsomebody made. Telling them apart is the whole reason this replaced `is_active` rather\nthan sitting beside it \u2014 a boolean plus a flag can disagree, and then every reader has to\npick a winner.",
        "enum": [
          "pending",
          "active",
          "withdrawn"
        ],
        "title": "AdminStatus",
        "type": "string"
      },
      "AdminUserCreate": {
        "properties": {
          "auth_method": {
            "$ref": "#/components/schemas/AuthMethod",
            "default": "google"
          },
          "email": {
            "format": "email",
            "title": "Email",
            "type": "string"
          },
          "name": {
            "anyOf": [
              {
                "maxLength": 200,
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Name"
          },
          "role": {
            "$ref": "#/components/schemas/AdminRole",
            "default": "host"
          },
          "temporary_password": {
            "anyOf": [
              {
                "maxLength": 256,
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Temporary Password"
          },
          "username": {
            "anyOf": [
              {
                "maxLength": 64,
                "minLength": 3,
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Username"
          }
        },
        "required": [
          "email"
        ],
        "title": "AdminUserCreate",
        "type": "object"
      },
      "AdminUserRead": {
        "description": "What the roster screen shows. Deliberately has nowhere to put a credential.",
        "properties": {
          "auth_method": {
            "$ref": "#/components/schemas/AuthMethod"
          },
          "email": {
            "title": "Email",
            "type": "string"
          },
          "first_seen_at": {
            "anyOf": [
              {
                "format": "date-time",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "First Seen At"
          },
          "id": {
            "format": "uuid",
            "title": "Id",
            "type": "string"
          },
          "last_login_at": {
            "anyOf": [
              {
                "format": "date-time",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Last Login At"
          },
          "locked": {
            "title": "Locked",
            "type": "boolean"
          },
          "name": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Name"
          },
          "owned_event_count": {
            "title": "Owned Event Count",
            "type": "integer"
          },
          "role": {
            "$ref": "#/components/schemas/AdminRole"
          },
          "status": {
            "$ref": "#/components/schemas/AdminStatus"
          },
          "temporary_password_outstanding": {
            "title": "Temporary Password Outstanding",
            "type": "boolean"
          },
          "username": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Username"
          }
        },
        "required": [
          "id",
          "email",
          "name",
          "username",
          "role",
          "status",
          "auth_method",
          "temporary_password_outstanding",
          "locked",
          "first_seen_at",
          "last_login_at",
          "owned_event_count"
        ],
        "title": "AdminUserRead",
        "type": "object"
      },
      "AdminUserUpdate": {
        "description": "`auth_method` is absent on purpose: kind is fixed at creation (design D16).",
        "properties": {
          "email": {
            "anyOf": [
              {
                "format": "email",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Email"
          },
          "name": {
            "anyOf": [
              {
                "maxLength": 200,
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Name"
          },
          "role": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/AdminRole"
              },
              {
                "type": "null"
              }
            ]
          },
          "username": {
            "anyOf": [
              {
                "maxLength": 64,
                "minLength": 3,
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Username"
          }
        },
        "title": "AdminUserUpdate",
        "type": "object"
      },
      "AggregateStats": {
        "properties": {
          "guest_records": {
            "title": "Guest Records",
            "type": "integer"
          },
          "overall_response_rate": {
            "title": "Overall Response Rate",
            "type": "number"
          },
          "total_headcount": {
            "title": "Total Headcount",
            "type": "integer"
          }
        },
        "required": [
          "guest_records",
          "total_headcount",
          "overall_response_rate"
        ],
        "title": "AggregateStats",
        "type": "object"
      },
      "AuthMethod": {
        "description": "How one account proves who it is \u2014 fixed at creation, never edited (design D16).\n\nAn account reachable by both a password and Google is only ever as strong as its\npassword, however well the Google account is protected. Converting between kinds has the\nsame problem in one step, and is exactly what an attacker holding a super-admin session\nwould reach for. Moving someone between kinds is create-new-and-withdraw-old.",
        "enum": [
          "google",
          "password"
        ],
        "title": "AuthMethod",
        "type": "string"
      },
      "BatchProblemRead": {
        "properties": {
          "guest_name": {
            "title": "Guest Name",
            "type": "string"
          },
          "reason": {
            "title": "Reason",
            "type": "string"
          },
          "status": {
            "title": "Status",
            "type": "string"
          }
        },
        "required": [
          "guest_name",
          "status",
          "reason"
        ],
        "title": "BatchProblemRead",
        "type": "object"
      },
      "BatchProgressRead": {
        "properties": {
          "batch_id": {
            "format": "uuid",
            "title": "Batch Id",
            "type": "string"
          },
          "dry_run": {
            "title": "Dry Run",
            "type": "boolean"
          },
          "failed": {
            "title": "Failed",
            "type": "integer"
          },
          "finished": {
            "title": "Finished",
            "type": "boolean"
          },
          "problems": {
            "items": {
              "$ref": "#/components/schemas/BatchProblemRead"
            },
            "title": "Problems",
            "type": "array"
          },
          "problems_capped": {
            "title": "Problems Capped",
            "type": "boolean"
          },
          "sent": {
            "title": "Sent",
            "type": "integer"
          },
          "skipped": {
            "title": "Skipped",
            "type": "integer"
          },
          "total": {
            "title": "Total",
            "type": "integer"
          },
          "waiting": {
            "title": "Waiting",
            "type": "integer"
          }
        },
        "required": [
          "batch_id",
          "total",
          "waiting",
          "sent",
          "failed",
          "skipped",
          "finished",
          "problems",
          "problems_capped",
          "dry_run"
        ],
        "title": "BatchProgressRead",
        "type": "object"
      },
      "Body_activate_admin_user_api_admin_users__admin_id__activate_post": {
        "properties": {
          "role": {
            "$ref": "#/components/schemas/AdminRole",
            "default": "host"
          }
        },
        "title": "Body_activate_admin_user_api_admin_users__admin_id__activate_post",
        "type": "object"
      },
      "Body_commit_import_api_admin_events__event_id__guests_import_post": {
        "properties": {
          "file": {
            "contentMediaType": "application/octet-stream",
            "title": "File",
            "type": "string"
          }
        },
        "required": [
          "file"
        ],
        "title": "Body_commit_import_api_admin_events__event_id__guests_import_post",
        "type": "object"
      },
      "Body_preview_import_api_admin_events__event_id__guests_import_preview_post": {
        "properties": {
          "file": {
            "contentMediaType": "application/octet-stream",
            "title": "File",
            "type": "string"
          }
        },
        "required": [
          "file"
        ],
        "title": "Body_preview_import_api_admin_events__event_id__guests_import_preview_post",
        "type": "object"
      },
      "Body_upload_design_api_admin_events__event_id__card_designs_post": {
        "properties": {
          "alt_bn": {
            "default": "",
            "title": "Alt Bn",
            "type": "string"
          },
          "alt_en": {
            "default": "",
            "title": "Alt En",
            "type": "string"
          },
          "assets": {
            "anyOf": [
              {
                "items": {
                  "contentMediaType": "application/octet-stream",
                  "type": "string"
                },
                "type": "array"
              },
              {
                "type": "null"
              }
            ],
            "description": "Every file the card references",
            "title": "Assets"
          },
          "document": {
            "contentMediaType": "application/octet-stream",
            "description": "The card's HTML file",
            "title": "Document",
            "type": "string"
          },
          "og_image": {
            "anyOf": [
              {
                "contentMediaType": "application/octet-stream",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "description": "1200x630 PNG or JPEG shown when a link to this event is shared",
            "title": "Og Image"
          }
        },
        "required": [
          "document"
        ],
        "title": "Body_upload_design_api_admin_events__event_id__card_designs_post",
        "type": "object"
      },
      "BroadcastResult": {
        "properties": {
          "queued": {
            "title": "Queued",
            "type": "integer"
          },
          "skipped_no_email": {
            "title": "Skipped No Email",
            "type": "integer"
          }
        },
        "required": [
          "queued",
          "skipped_no_email"
        ],
        "title": "BroadcastResult",
        "type": "object"
      },
      "BulkComposeRead": {
        "properties": {
          "deliverable": {
            "title": "Deliverable",
            "type": "integer"
          },
          "event_published": {
            "title": "Event Published",
            "type": "boolean"
          },
          "event_title": {
            "title": "Event Title",
            "type": "string"
          },
          "exclusions": {
            "items": {
              "$ref": "#/components/schemas/ExclusionRead"
            },
            "title": "Exclusions",
            "type": "array"
          },
          "in_quiet_hours": {
            "title": "In Quiet Hours",
            "type": "boolean"
          },
          "local_time": {
            "title": "Local Time",
            "type": "string"
          },
          "missing": {
            "title": "Missing",
            "type": "integer"
          },
          "panes": {
            "items": {
              "$ref": "#/components/schemas/ComposedPaneRead"
            },
            "title": "Panes",
            "type": "array"
          },
          "placeholders": {
            "items": {
              "type": "string"
            },
            "title": "Placeholders",
            "type": "array"
          },
          "selected": {
            "title": "Selected",
            "type": "integer"
          }
        },
        "required": [
          "event_title",
          "panes",
          "selected",
          "deliverable",
          "exclusions",
          "missing",
          "placeholders",
          "in_quiet_hours",
          "local_time",
          "event_published"
        ],
        "title": "BulkComposeRead",
        "type": "object"
      },
      "BulkComposeRequest": {
        "properties": {
          "guest_ids": {
            "items": {
              "format": "uuid",
              "type": "string"
            },
            "maxItems": 2000,
            "minItems": 1,
            "title": "Guest Ids",
            "type": "array"
          }
        },
        "required": [
          "guest_ids"
        ],
        "title": "BulkComposeRequest",
        "type": "object"
      },
      "BulkPaneWrite": {
        "description": "One pane as the admin edited it.",
        "properties": {
          "body": {
            "title": "Body",
            "type": "string"
          },
          "footer": {
            "title": "Footer",
            "type": "string"
          },
          "header": {
            "title": "Header",
            "type": "string"
          },
          "invitation_type": {
            "$ref": "#/components/schemas/InvitationType"
          },
          "locale": {
            "$ref": "#/components/schemas/Locale"
          },
          "subject": {
            "title": "Subject",
            "type": "string"
          }
        },
        "required": [
          "locale",
          "invitation_type",
          "subject",
          "header",
          "body",
          "footer"
        ],
        "title": "BulkPaneWrite",
        "type": "object"
      },
      "CardDesignRead": {
        "properties": {
          "assets": {
            "additionalProperties": {
              "type": "string"
            },
            "title": "Assets",
            "type": "object"
          },
          "config": {
            "additionalProperties": true,
            "title": "Config",
            "type": "object"
          },
          "created_at": {
            "format": "date-time",
            "title": "Created At",
            "type": "string"
          },
          "event_id": {
            "format": "uuid",
            "title": "Event Id",
            "type": "string"
          },
          "id": {
            "format": "uuid",
            "title": "Id",
            "type": "string"
          },
          "preview_image": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/PreviewImage"
              },
              {
                "type": "null"
              }
            ]
          },
          "published_at": {
            "anyOf": [
              {
                "format": "date-time",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Published At"
          },
          "renderer": {
            "$ref": "#/components/schemas/CardRenderer"
          },
          "status": {
            "$ref": "#/components/schemas/CardDesignStatus"
          },
          "total_bytes": {
            "title": "Total Bytes",
            "type": "integer"
          },
          "version": {
            "title": "Version",
            "type": "integer"
          },
          "warnings": {
            "items": {
              "type": "string"
            },
            "title": "Warnings",
            "type": "array"
          }
        },
        "required": [
          "id",
          "event_id",
          "renderer",
          "status",
          "version",
          "config",
          "assets",
          "total_bytes",
          "preview_image",
          "published_at",
          "created_at"
        ],
        "title": "CardDesignRead",
        "type": "object"
      },
      "CardDesignStatus": {
        "enum": [
          "draft",
          "published"
        ],
        "title": "CardDesignStatus",
        "type": "string"
      },
      "CardRead": {
        "description": "The published card design, resolved for rendering (design D1, D3).\n\nCarries no guest data of any kind. The card is fixed for the event \u2014 the guest's name\nlives in the header above it and nowhere else (spec invitation-presentation), which is\nalso what lets the same document be served to every guest.",
        "properties": {
          "alt_bn": {
            "default": "",
            "title": "Alt Bn",
            "type": "string"
          },
          "alt_en": {
            "default": "",
            "title": "Alt En",
            "type": "string"
          },
          "document": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Document"
          },
          "height": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Height"
          },
          "image_url": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Image Url"
          },
          "renderer": {
            "$ref": "#/components/schemas/CardRenderer"
          },
          "width": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Width"
          }
        },
        "required": [
          "renderer"
        ],
        "title": "CardRead",
        "type": "object"
      },
      "CardRenderer": {
        "description": "How a published card design is drawn (design D1).\n\nHTML is the one v1 implements: the design team authors a self-contained HTML/CSS file\nand it is rendered inside a shadow root (design D3). IMAGE is implemented as the\nfallback for a design delivered as flat artwork.\n\nCOMPONENT and LAYERED are declared but have no renderer. They are kept so the enum, the\nmigration and the stored values never have to change when one arrives \u2014 the API rejects\nthem on write until then, which is a code change rather than a schema one.",
        "enum": [
          "html",
          "image",
          "component",
          "layered"
        ],
        "title": "CardRenderer",
        "type": "string"
      },
      "Channel": {
        "enum": [
          "email",
          "whatsapp",
          "sms"
        ],
        "title": "Channel",
        "type": "string"
      },
      "ComposedMessageRead": {
        "description": "The default message, in the parts the admin edits.",
        "properties": {
          "blocked_reason": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Blocked Reason"
          },
          "body": {
            "title": "Body",
            "type": "string"
          },
          "event_published": {
            "title": "Event Published",
            "type": "boolean"
          },
          "event_title": {
            "title": "Event Title",
            "type": "string"
          },
          "footer": {
            "title": "Footer",
            "type": "string"
          },
          "header": {
            "title": "Header",
            "type": "string"
          },
          "in_quiet_hours": {
            "title": "In Quiet Hours",
            "type": "boolean"
          },
          "invite_url": {
            "title": "Invite Url",
            "type": "string"
          },
          "last_sent_at": {
            "anyOf": [
              {
                "format": "date-time",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Last Sent At"
          },
          "local_time": {
            "title": "Local Time",
            "type": "string"
          },
          "preview": {
            "$ref": "#/components/schemas/AdminPreviewRead"
          },
          "subject": {
            "title": "Subject",
            "type": "string"
          },
          "to_email": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "To Email"
          }
        },
        "required": [
          "subject",
          "header",
          "body",
          "footer",
          "invite_url",
          "preview",
          "to_email",
          "event_title",
          "last_sent_at",
          "blocked_reason",
          "in_quiet_hours",
          "local_time",
          "event_published"
        ],
        "title": "ComposedMessageRead",
        "type": "object"
      },
      "ComposedPaneRead": {
        "description": "One pane: the message for one invitation type in one locale, with placeholders intact.",
        "properties": {
          "body": {
            "title": "Body",
            "type": "string"
          },
          "footer": {
            "title": "Footer",
            "type": "string"
          },
          "header": {
            "title": "Header",
            "type": "string"
          },
          "invitation_type": {
            "$ref": "#/components/schemas/InvitationType"
          },
          "locale": {
            "$ref": "#/components/schemas/Locale"
          },
          "recipient_count": {
            "title": "Recipient Count",
            "type": "integer"
          },
          "subject": {
            "title": "Subject",
            "type": "string"
          }
        },
        "required": [
          "locale",
          "invitation_type",
          "subject",
          "header",
          "body",
          "footer",
          "recipient_count"
        ],
        "title": "ComposedPaneRead",
        "type": "object"
      },
      "CoupleRead": {
        "properties": {
          "bride_name": {
            "title": "Bride Name",
            "type": "string"
          },
          "default_locale": {
            "$ref": "#/components/schemas/Locale"
          },
          "groom_name": {
            "title": "Groom Name",
            "type": "string"
          },
          "host_contact_phone": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Host Contact Phone"
          }
        },
        "required": [
          "bride_name",
          "groom_name",
          "default_locale",
          "host_contact_phone"
        ],
        "title": "CoupleRead",
        "type": "object"
      },
      "CurrentRsvpRead": {
        "description": "The guest's existing answer, so a returning visitor sees it instead of a blank\nform (FR-1.12).",
        "properties": {
          "dietary_notes": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Dietary Notes"
          },
          "message_to_couple": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Message To Couple"
          },
          "party_size": {
            "title": "Party Size",
            "type": "integer"
          },
          "responded_at": {
            "format": "date-time",
            "title": "Responded At",
            "type": "string"
          },
          "response": {
            "$ref": "#/components/schemas/RsvpResponse"
          }
        },
        "required": [
          "response",
          "party_size",
          "message_to_couple",
          "dietary_notes",
          "responded_at"
        ],
        "title": "CurrentRsvpRead",
        "type": "object"
      },
      "DashboardStats": {
        "properties": {
          "aggregate": {
            "$ref": "#/components/schemas/AggregateStats"
          },
          "events": {
            "items": {
              "$ref": "#/components/schemas/EventStats"
            },
            "title": "Events",
            "type": "array"
          },
          "generated_at": {
            "format": "date-time",
            "title": "Generated At",
            "type": "string"
          },
          "owns_nothing": {
            "default": false,
            "title": "Owns Nothing",
            "type": "boolean"
          }
        },
        "required": [
          "events",
          "aggregate",
          "generated_at"
        ],
        "title": "DashboardStats",
        "type": "object"
      },
      "DateChangeBroadcast": {
        "description": "Explicit second step. Re-planning is automatic because leaving stale jobs queued is\nnever right; telling 800 people is a decision, so it stays a separate button.",
        "properties": {
          "audience_accepted_only": {
            "default": true,
            "title": "Audience Accepted Only",
            "type": "boolean"
          }
        },
        "title": "DateChangeBroadcast",
        "type": "object"
      },
      "DevSignInRequest": {
        "description": "Dev bypass only \u2014 rejected unless AUTH_DEV_BYPASS is on and ENVIRONMENT is dev.",
        "properties": {
          "email": {
            "title": "Email",
            "type": "string"
          }
        },
        "required": [
          "email"
        ],
        "title": "DevSignInRequest",
        "type": "object"
      },
      "EventCreate": {
        "description": "What an admin fills in to bring an event into existence (design D12).\n\n`slug` is absent on purpose: it is derived from the name and allocated by the server, so\na caller cannot mint a public address by hand or collide with an existing one.",
        "properties": {
          "capacity": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Capacity"
          },
          "dress_code": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Dress Code"
          },
          "ends_at": {
            "anyOf": [
              {
                "format": "date-time",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Ends At"
          },
          "host_name_1": {
            "maxLength": 200,
            "minLength": 1,
            "title": "Host Name 1",
            "type": "string"
          },
          "host_name_2": {
            "anyOf": [
              {
                "maxLength": 200,
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Host Name 2"
          },
          "host_phone": {
            "minLength": 1,
            "title": "Host Phone",
            "type": "string"
          },
          "invitation_messages": {
            "additionalProperties": {
              "additionalProperties": {
                "type": "string"
              },
              "type": "object"
            },
            "title": "Invitation Messages",
            "type": "object"
          },
          "is_published": {
            "default": false,
            "title": "Is Published",
            "type": "boolean"
          },
          "map_url": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Map Url"
          },
          "notes": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Notes"
          },
          "owner_admin_id": {
            "anyOf": [
              {
                "format": "uuid",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Owner Admin Id"
          },
          "rsvp_deadline": {
            "anyOf": [
              {
                "format": "date-time",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Rsvp Deadline"
          },
          "starts_at": {
            "format": "date-time",
            "title": "Starts At",
            "type": "string"
          },
          "theme_key": {
            "default": "classic",
            "title": "Theme Key",
            "type": "string"
          },
          "title_bn": {
            "maxLength": 200,
            "minLength": 1,
            "title": "Title Bn",
            "type": "string"
          },
          "title_en": {
            "maxLength": 200,
            "minLength": 1,
            "title": "Title En",
            "type": "string"
          },
          "type": {
            "$ref": "#/components/schemas/EventType"
          },
          "venue_address": {
            "minLength": 1,
            "title": "Venue Address",
            "type": "string"
          },
          "venue_name": {
            "minLength": 1,
            "title": "Venue Name",
            "type": "string"
          }
        },
        "required": [
          "type",
          "title_en",
          "title_bn",
          "starts_at",
          "venue_name",
          "venue_address",
          "host_name_1",
          "host_phone"
        ],
        "title": "EventCreate",
        "type": "object"
      },
      "EventDeleteImpact": {
        "description": "What deleting an event destroys, so the confirmation can state it (spec).",
        "properties": {
          "guests": {
            "title": "Guests",
            "type": "integer"
          },
          "responses": {
            "title": "Responses",
            "type": "integer"
          }
        },
        "required": [
          "guests",
          "responses"
        ],
        "title": "EventDeleteImpact",
        "type": "object"
      },
      "EventRead": {
        "description": "Public event detail. Everything here is printed on a card anyway.",
        "properties": {
          "cover_image_url": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Cover Image Url"
          },
          "dress_code": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Dress Code"
          },
          "ends_at": {
            "anyOf": [
              {
                "format": "date-time",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Ends At"
          },
          "host_name_1": {
            "title": "Host Name 1",
            "type": "string"
          },
          "host_name_2": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Host Name 2"
          },
          "host_phone": {
            "title": "Host Phone",
            "type": "string"
          },
          "id": {
            "format": "uuid",
            "title": "Id",
            "type": "string"
          },
          "map_url": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Map Url"
          },
          "music_url": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Music Url"
          },
          "notes": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Notes"
          },
          "rsvp_deadline": {
            "anyOf": [
              {
                "format": "date-time",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Rsvp Deadline"
          },
          "slug": {
            "title": "Slug",
            "type": "string"
          },
          "starts_at": {
            "format": "date-time",
            "title": "Starts At",
            "type": "string"
          },
          "theme_key": {
            "title": "Theme Key",
            "type": "string"
          },
          "title_bn": {
            "title": "Title Bn",
            "type": "string"
          },
          "title_en": {
            "title": "Title En",
            "type": "string"
          },
          "type": {
            "$ref": "#/components/schemas/EventType"
          },
          "venue_address": {
            "title": "Venue Address",
            "type": "string"
          },
          "venue_name": {
            "title": "Venue Name",
            "type": "string"
          }
        },
        "required": [
          "id",
          "type",
          "slug",
          "title_bn",
          "title_en",
          "starts_at",
          "ends_at",
          "venue_name",
          "venue_address",
          "map_url",
          "dress_code",
          "notes",
          "host_name_1",
          "host_name_2",
          "host_phone",
          "theme_key",
          "cover_image_url",
          "music_url",
          "rsvp_deadline"
        ],
        "title": "EventRead",
        "type": "object"
      },
      "EventStats": {
        "properties": {
          "accepted": {
            "title": "Accepted",
            "type": "integer"
          },
          "cancelled": {
            "title": "Cancelled",
            "type": "integer"
          },
          "capacity": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Capacity"
          },
          "days_to_event": {
            "title": "Days To Event",
            "type": "integer"
          },
          "declined": {
            "title": "Declined",
            "type": "integer"
          },
          "event_id": {
            "title": "Event Id",
            "type": "string"
          },
          "headcount": {
            "title": "Headcount",
            "type": "integer"
          },
          "invited": {
            "title": "Invited",
            "type": "integer"
          },
          "no_email_count": {
            "title": "No Email Count",
            "type": "integer"
          },
          "over_capacity": {
            "title": "Over Capacity",
            "type": "boolean"
          },
          "pending": {
            "title": "Pending",
            "type": "integer"
          },
          "response_rate": {
            "title": "Response Rate",
            "type": "number"
          },
          "slug": {
            "title": "Slug",
            "type": "string"
          },
          "starts_at": {
            "format": "date-time",
            "title": "Starts At",
            "type": "string"
          },
          "title_bn": {
            "title": "Title Bn",
            "type": "string"
          },
          "title_en": {
            "title": "Title En",
            "type": "string"
          },
          "type": {
            "title": "Type",
            "type": "string"
          }
        },
        "required": [
          "event_id",
          "type",
          "title_en",
          "title_bn",
          "slug",
          "starts_at",
          "days_to_event",
          "capacity",
          "invited",
          "accepted",
          "declined",
          "pending",
          "cancelled",
          "headcount",
          "response_rate",
          "over_capacity",
          "no_email_count"
        ],
        "title": "EventStats",
        "type": "object"
      },
      "EventType": {
        "enum": [
          "mehedi",
          "marriage",
          "walima"
        ],
        "title": "EventType",
        "type": "string"
      },
      "EventUpdate": {
        "description": "Every field optional \u2014 a PATCH that only moves the date must not blank the venue.\n\n`slug` is not updatable: it is the address on already-printed QR cards (design D12).\n\n`owner_admin_id` is absent for the same kind of reason, and it is load-bearing: editing an\nevent must never reassign it, and a Host must never reassign anything. Leaving the field\noff the model means no request body can express it \u2014 a check that cannot be forgotten,\nunlike one written in the handler. Transfer lives on its own Super-Admin-only endpoint.",
        "properties": {
          "capacity": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Capacity"
          },
          "dress_code": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Dress Code"
          },
          "ends_at": {
            "anyOf": [
              {
                "format": "date-time",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Ends At"
          },
          "host_name_1": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Host Name 1"
          },
          "host_name_2": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Host Name 2"
          },
          "host_phone": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Host Phone"
          },
          "invitation_messages": {
            "anyOf": [
              {
                "additionalProperties": {
                  "additionalProperties": {
                    "type": "string"
                  },
                  "type": "object"
                },
                "type": "object"
              },
              {
                "type": "null"
              }
            ],
            "title": "Invitation Messages"
          },
          "is_published": {
            "anyOf": [
              {
                "type": "boolean"
              },
              {
                "type": "null"
              }
            ],
            "title": "Is Published"
          },
          "map_url": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Map Url"
          },
          "notes": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Notes"
          },
          "rsvp_deadline": {
            "anyOf": [
              {
                "format": "date-time",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Rsvp Deadline"
          },
          "starts_at": {
            "anyOf": [
              {
                "format": "date-time",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Starts At"
          },
          "theme_key": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Theme Key"
          },
          "title_bn": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Title Bn"
          },
          "title_en": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Title En"
          },
          "venue_address": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Venue Address"
          },
          "venue_name": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Venue Name"
          }
        },
        "title": "EventUpdate",
        "type": "object"
      },
      "EventUpdateResult": {
        "properties": {
          "broadcast_recommended": {
            "default": false,
            "title": "Broadcast Recommended",
            "type": "boolean"
          },
          "date_changed": {
            "title": "Date Changed",
            "type": "boolean"
          },
          "event": {
            "$ref": "#/components/schemas/AdminEventRead"
          },
          "reminders_cancelled": {
            "default": 0,
            "title": "Reminders Cancelled",
            "type": "integer"
          },
          "reminders_created": {
            "default": 0,
            "title": "Reminders Created",
            "type": "integer"
          },
          "reminders_rescheduled": {
            "default": 0,
            "title": "Reminders Rescheduled",
            "type": "integer"
          }
        },
        "required": [
          "event",
          "date_changed"
        ],
        "title": "EventUpdateResult",
        "type": "object"
      },
      "ExclusionRead": {
        "description": "One reason some of the selection will not be emailed, and how many (D10).",
        "properties": {
          "code": {
            "title": "Code",
            "type": "string"
          },
          "count": {
            "title": "Count",
            "type": "integer"
          },
          "reason": {
            "title": "Reason",
            "type": "string"
          }
        },
        "required": [
          "code",
          "reason",
          "count"
        ],
        "title": "ExclusionRead",
        "type": "object"
      },
      "GoogleSignInRequest": {
        "properties": {
          "id_token": {
            "title": "Id Token",
            "type": "string"
          }
        },
        "required": [
          "id_token"
        ],
        "title": "GoogleSignInRequest",
        "type": "object"
      },
      "GreetingRead": {
        "description": "The header sentence, resolved server-side (design D9, task 4.3).\n\nBoth locales are returned rather than one, because the guest can toggle language on the\npage without a round trip (design D13). Neither is ever empty: resolution falls through\nto a built-in default, so the frontend has no empty-greeting branch to get wrong.",
        "properties": {
          "invitation_type": {
            "$ref": "#/components/schemas/InvitationType"
          },
          "message_bn": {
            "title": "Message Bn",
            "type": "string"
          },
          "message_en": {
            "title": "Message En",
            "type": "string"
          }
        },
        "required": [
          "invitation_type",
          "message_en",
          "message_bn"
        ],
        "title": "GreetingRead",
        "type": "object"
      },
      "GuestIdsRead": {
        "description": "Every guest matching a set of filters, for select-all-matching (D2).",
        "properties": {
          "guest_ids": {
            "items": {
              "format": "uuid",
              "type": "string"
            },
            "title": "Guest Ids",
            "type": "array"
          },
          "total": {
            "title": "Total",
            "type": "integer"
          }
        },
        "required": [
          "guest_ids",
          "total"
        ],
        "title": "GuestIdsRead",
        "type": "object"
      },
      "GuestPage": {
        "properties": {
          "items": {
            "items": {
              "$ref": "#/components/schemas/GuestRead"
            },
            "title": "Items",
            "type": "array"
          },
          "page": {
            "title": "Page",
            "type": "integer"
          },
          "page_size": {
            "title": "Page Size",
            "type": "integer"
          },
          "total": {
            "title": "Total",
            "type": "integer"
          }
        },
        "required": [
          "items",
          "total",
          "page",
          "page_size"
        ],
        "title": "GuestPage",
        "type": "object"
      },
      "GuestPrefill": {
        "description": "Only what the RSVP form needs to pre-fill (FR-2.5). Never sent on the open route.",
        "properties": {
          "email": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Email"
          },
          "full_name": {
            "title": "Full Name",
            "type": "string"
          },
          "phone_e164": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Phone E164"
          },
          "preferred_locale": {
            "$ref": "#/components/schemas/Locale"
          }
        },
        "required": [
          "full_name",
          "email",
          "phone_e164",
          "preferred_locale"
        ],
        "title": "GuestPrefill",
        "type": "object"
      },
      "GuestRead": {
        "properties": {
          "created_at": {
            "format": "date-time",
            "title": "Created At",
            "type": "string"
          },
          "do_not_contact": {
            "title": "Do Not Contact",
            "type": "boolean"
          },
          "email": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Email"
          },
          "email_invalid": {
            "title": "Email Invalid",
            "type": "boolean"
          },
          "event_id": {
            "format": "uuid",
            "title": "Event Id",
            "type": "string"
          },
          "full_name": {
            "title": "Full Name",
            "type": "string"
          },
          "group_tag": {
            "items": {
              "type": "string"
            },
            "title": "Group Tag",
            "type": "array"
          },
          "id": {
            "format": "uuid",
            "title": "Id",
            "type": "string"
          },
          "invitation_type": {
            "$ref": "#/components/schemas/InvitationType"
          },
          "invitations": {
            "items": {
              "$ref": "#/components/schemas/InvitationSummary"
            },
            "title": "Invitations",
            "type": "array"
          },
          "phone_e164": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Phone E164"
          },
          "preferred_channel": {
            "$ref": "#/components/schemas/PreferredChannel"
          },
          "preferred_locale": {
            "$ref": "#/components/schemas/Locale"
          },
          "side": {
            "$ref": "#/components/schemas/GuestSide"
          },
          "source": {
            "$ref": "#/components/schemas/GuestSource"
          },
          "whatsapp_phone_e164": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Whatsapp Phone E164"
          }
        },
        "required": [
          "id",
          "event_id",
          "full_name",
          "email",
          "phone_e164",
          "whatsapp_phone_e164",
          "side",
          "group_tag",
          "preferred_locale",
          "preferred_channel",
          "invitation_type",
          "source",
          "do_not_contact",
          "email_invalid",
          "created_at",
          "invitations"
        ],
        "title": "GuestRead",
        "type": "object"
      },
      "GuestSide": {
        "enum": [
          "bride",
          "groom",
          "common"
        ],
        "title": "GuestSide",
        "type": "string"
      },
      "GuestSource": {
        "enum": [
          "manual",
          "csv_import",
          "self_registered"
        ],
        "title": "GuestSource",
        "type": "string"
      },
      "GuestWrite": {
        "properties": {
          "email": {
            "anyOf": [
              {
                "format": "email",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Email"
          },
          "full_name": {
            "maxLength": 200,
            "minLength": 1,
            "title": "Full Name",
            "type": "string"
          },
          "group_tag": {
            "items": {
              "type": "string"
            },
            "title": "Group Tag",
            "type": "array"
          },
          "invitation_type": {
            "$ref": "#/components/schemas/InvitationType",
            "default": "single"
          },
          "max_guests": {
            "default": 4,
            "maximum": 20.0,
            "minimum": 1.0,
            "title": "Max Guests",
            "type": "integer"
          },
          "phone": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Phone"
          },
          "preferred_channel": {
            "$ref": "#/components/schemas/PreferredChannel",
            "default": "auto"
          },
          "preferred_locale": {
            "$ref": "#/components/schemas/Locale",
            "default": "en"
          },
          "side": {
            "$ref": "#/components/schemas/GuestSide",
            "default": "common"
          },
          "whatsapp_phone": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Whatsapp Phone"
          }
        },
        "required": [
          "full_name"
        ],
        "title": "GuestWrite",
        "type": "object"
      },
      "HTTPValidationError": {
        "properties": {
          "detail": {
            "items": {
              "$ref": "#/components/schemas/ValidationError"
            },
            "title": "Detail",
            "type": "array"
          }
        },
        "title": "HTTPValidationError",
        "type": "object"
      },
      "ImportPreview": {
        "properties": {
          "duplicates": {
            "title": "Duplicates",
            "type": "integer"
          },
          "errors": {
            "items": {
              "$ref": "#/components/schemas/ImportRowReport"
            },
            "title": "Errors",
            "type": "array"
          },
          "invalid_rows": {
            "title": "Invalid Rows",
            "type": "integer"
          },
          "preview": {
            "items": {
              "$ref": "#/components/schemas/ImportRowReport"
            },
            "title": "Preview",
            "type": "array"
          },
          "total_rows": {
            "title": "Total Rows",
            "type": "integer"
          },
          "valid_rows": {
            "title": "Valid Rows",
            "type": "integer"
          }
        },
        "required": [
          "total_rows",
          "valid_rows",
          "invalid_rows",
          "duplicates",
          "preview",
          "errors"
        ],
        "title": "ImportPreview",
        "type": "object"
      },
      "ImportResult": {
        "properties": {
          "errors": {
            "items": {
              "$ref": "#/components/schemas/ImportRowReport"
            },
            "title": "Errors",
            "type": "array"
          },
          "imported": {
            "title": "Imported",
            "type": "integer"
          },
          "merged": {
            "title": "Merged",
            "type": "integer"
          },
          "skipped": {
            "title": "Skipped",
            "type": "integer"
          }
        },
        "required": [
          "imported",
          "merged",
          "skipped",
          "errors"
        ],
        "title": "ImportResult",
        "type": "object"
      },
      "ImportRowReport": {
        "properties": {
          "errors": {
            "items": {
              "type": "string"
            },
            "title": "Errors",
            "type": "array"
          },
          "full_name": {
            "title": "Full Name",
            "type": "string"
          },
          "line_number": {
            "title": "Line Number",
            "type": "integer"
          },
          "would_merge": {
            "default": false,
            "title": "Would Merge",
            "type": "boolean"
          }
        },
        "required": [
          "line_number",
          "full_name",
          "errors"
        ],
        "title": "ImportRowReport",
        "type": "object"
      },
      "InvitationMessagesWrite": {
        "description": "The whole map, not a patch of one key.\n\nSending the map entire means clearing a message is expressible \u2014 with a partial update,\nan absent key and an intentionally emptied one are the same request.",
        "properties": {
          "messages": {
            "additionalProperties": {
              "additionalProperties": {
                "type": "string"
              },
              "type": "object"
            },
            "title": "Messages",
            "type": "object"
          }
        },
        "title": "InvitationMessagesWrite",
        "type": "object"
      },
      "InvitationStatus": {
        "enum": [
          "pending",
          "opened",
          "accepted",
          "declined",
          "cancelled",
          "expired"
        ],
        "title": "InvitationStatus",
        "type": "string"
      },
      "InvitationSummary": {
        "properties": {
          "event_published": {
            "title": "Event Published",
            "type": "boolean"
          },
          "event_title_en": {
            "title": "Event Title En",
            "type": "string"
          },
          "event_type": {
            "$ref": "#/components/schemas/EventType"
          },
          "id": {
            "format": "uuid",
            "title": "Id",
            "type": "string"
          },
          "max_guests": {
            "title": "Max Guests",
            "type": "integer"
          },
          "opened_at": {
            "anyOf": [
              {
                "format": "date-time",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Opened At"
          },
          "party_size": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Party Size"
          },
          "responded_at": {
            "anyOf": [
              {
                "format": "date-time",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Responded At"
          },
          "short_code": {
            "title": "Short Code",
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/InvitationStatus"
          },
          "token": {
            "title": "Token",
            "type": "string"
          }
        },
        "required": [
          "id",
          "event_type",
          "event_title_en",
          "status",
          "party_size",
          "max_guests",
          "token",
          "short_code",
          "opened_at",
          "responded_at",
          "event_published"
        ],
        "title": "InvitationSummary",
        "type": "object"
      },
      "InvitationType": {
        "description": "Which greeting a guest is addressed with (spec invitation-presentation).\n\nCopy only. `invitation.max_guests` remains the sole party-size ceiling \u2014 a family\ninvitation with one seat is a contradiction the admin form warns about rather than one\nthe system silently resolves, because raising a ceiling changes a headcount (design D6).",
        "enum": [
          "single",
          "family"
        ],
        "title": "InvitationType",
        "type": "string"
      },
      "LinkPreviewRead": {
        "description": "What a link to this invitation says when it is shown somewhere else (design D5).\n\nGuest-free on both routes, deliberately. These fields end up in meta tags that a chat\napplication fetches and caches, so they must be identical for every guest invited to the\nevent and derivable from public event information alone (design D3). The guest's name\nreaches their email and the admin panel by a different path \u2014 never this one.",
        "properties": {
          "canonical_url": {
            "title": "Canonical Url",
            "type": "string"
          },
          "description": {
            "title": "Description",
            "type": "string"
          },
          "image": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/PreviewImageRead"
              },
              {
                "type": "null"
              }
            ]
          },
          "site_name": {
            "title": "Site Name",
            "type": "string"
          },
          "title": {
            "title": "Title",
            "type": "string"
          }
        },
        "required": [
          "title",
          "description",
          "canonical_url",
          "site_name",
          "image"
        ],
        "title": "LinkPreviewRead",
        "type": "object"
      },
      "Locale": {
        "enum": [
          "bn",
          "en"
        ],
        "title": "Locale",
        "type": "string"
      },
      "ManualRsvpRequest": {
        "description": "A phone RSVP recorded on the guest's behalf (FR-4.6).",
        "properties": {
          "note": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Note"
          },
          "party_size": {
            "default": 1,
            "minimum": 1.0,
            "title": "Party Size",
            "type": "integer"
          },
          "response": {
            "enum": [
              "accepted",
              "declined"
            ],
            "title": "Response",
            "type": "string"
          }
        },
        "required": [
          "response"
        ],
        "title": "ManualRsvpRequest",
        "type": "object"
      },
      "MessageJobStatus": {
        "enum": [
          "queued",
          "sending",
          "sent",
          "delivered",
          "read",
          "failed",
          "skipped"
        ],
        "title": "MessageJobStatus",
        "type": "string"
      },
      "MessageLogEntry": {
        "properties": {
          "attempts": {
            "title": "Attempts",
            "type": "integer"
          },
          "channel": {
            "$ref": "#/components/schemas/Channel"
          },
          "error_code": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Error Code"
          },
          "error_message": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Error Message"
          },
          "id": {
            "format": "uuid",
            "title": "Id",
            "type": "string"
          },
          "invitation_id": {
            "format": "uuid",
            "title": "Invitation Id",
            "type": "string"
          },
          "provider_message_id": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Provider Message Id"
          },
          "scheduled_for": {
            "format": "date-time",
            "title": "Scheduled For",
            "type": "string"
          },
          "sent_at": {
            "anyOf": [
              {
                "format": "date-time",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Sent At"
          },
          "skip_reason": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Skip Reason"
          },
          "status": {
            "$ref": "#/components/schemas/MessageJobStatus"
          }
        },
        "required": [
          "id",
          "invitation_id",
          "channel",
          "status",
          "scheduled_for",
          "attempts",
          "provider_message_id",
          "error_code",
          "error_message",
          "skip_reason",
          "sent_at"
        ],
        "title": "MessageLogEntry",
        "type": "object"
      },
      "MessageLogPage": {
        "properties": {
          "counts_by_status": {
            "additionalProperties": {
              "type": "integer"
            },
            "title": "Counts By Status",
            "type": "object"
          },
          "items": {
            "items": {
              "$ref": "#/components/schemas/MessageLogEntry"
            },
            "title": "Items",
            "type": "array"
          },
          "total": {
            "title": "Total",
            "type": "integer"
          }
        },
        "required": [
          "items",
          "total",
          "counts_by_status"
        ],
        "title": "MessageLogPage",
        "type": "object"
      },
      "OpenEventRead": {
        "description": "Payload for `/e/{slug}` \u2014 no personal data, cacheable, identical for everyone.",
        "properties": {
          "card": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/CardRead"
              },
              {
                "type": "null"
              }
            ]
          },
          "couple": {
            "$ref": "#/components/schemas/CoupleRead"
          },
          "event": {
            "$ref": "#/components/schemas/EventRead"
          },
          "greeting": {
            "$ref": "#/components/schemas/GreetingRead"
          },
          "preview": {
            "$ref": "#/components/schemas/LinkPreviewRead"
          },
          "rsvp_open": {
            "title": "Rsvp Open",
            "type": "boolean"
          },
          "selectable_events": {
            "items": {
              "$ref": "#/components/schemas/EventRead"
            },
            "title": "Selectable Events",
            "type": "array"
          }
        },
        "required": [
          "event",
          "couple",
          "greeting",
          "card",
          "preview",
          "rsvp_open",
          "selectable_events"
        ],
        "title": "OpenEventRead",
        "type": "object"
      },
      "OpenRsvpConfirmation": {
        "description": "Open-link submissions can create several invitations at once.",
        "properties": {
          "confirmations": {
            "items": {
              "$ref": "#/components/schemas/RsvpConfirmation"
            },
            "title": "Confirmations",
            "type": "array"
          },
          "guest_id": {
            "format": "uuid",
            "title": "Guest Id",
            "type": "string"
          },
          "merged_into_existing_guest": {
            "title": "Merged Into Existing Guest",
            "type": "boolean"
          }
        },
        "required": [
          "guest_id",
          "merged_into_existing_guest",
          "confirmations"
        ],
        "title": "OpenRsvpConfirmation",
        "type": "object"
      },
      "OpenRsvpRequest": {
        "description": "Accept from the open/QR link, where the guest is unknown (FR-2.10).\n\nPhone is required here: it is the only reliable key for merging this submission into an\nexisting guest instead of creating a duplicate.",
        "properties": {
          "adults": {
            "anyOf": [
              {
                "minimum": 0.0,
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Adults"
          },
          "children": {
            "anyOf": [
              {
                "minimum": 0.0,
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Children"
          },
          "dietary_notes": {
            "anyOf": [
              {
                "maxLength": 1000,
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Dietary Notes"
          },
          "email": {
            "anyOf": [
              {
                "format": "email",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Email"
          },
          "event_types": {
            "items": {
              "$ref": "#/components/schemas/EventType"
            },
            "minItems": 1,
            "title": "Event Types",
            "type": "array"
          },
          "message_to_couple": {
            "anyOf": [
              {
                "maxLength": 500,
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Message To Couple"
          },
          "name": {
            "maxLength": 200,
            "minLength": 1,
            "title": "Name",
            "type": "string"
          },
          "party_size": {
            "default": 1,
            "minimum": 1.0,
            "title": "Party Size",
            "type": "integer"
          },
          "phone": {
            "title": "Phone",
            "type": "string"
          },
          "turnstile_token": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Turnstile Token"
          }
        },
        "required": [
          "name",
          "phone",
          "event_types"
        ],
        "title": "OpenRsvpRequest",
        "type": "object"
      },
      "OwnershipTransfer": {
        "properties": {
          "new_owner_id": {
            "format": "uuid",
            "title": "New Owner Id",
            "type": "string"
          }
        },
        "required": [
          "new_owner_id"
        ],
        "title": "OwnershipTransfer",
        "type": "object"
      },
      "PasswordChangeRequest": {
        "properties": {
          "current_password": {
            "maxLength": 256,
            "minLength": 1,
            "title": "Current Password",
            "type": "string"
          },
          "new_password": {
            "maxLength": 256,
            "minLength": 1,
            "title": "New Password",
            "type": "string"
          }
        },
        "required": [
          "current_password",
          "new_password"
        ],
        "title": "PasswordChangeRequest",
        "type": "object"
      },
      "PasswordSignInRequest": {
        "properties": {
          "password": {
            "maxLength": 256,
            "minLength": 1,
            "title": "Password",
            "type": "string"
          },
          "username": {
            "maxLength": 64,
            "minLength": 1,
            "title": "Username",
            "type": "string"
          }
        },
        "required": [
          "username",
          "password"
        ],
        "title": "PasswordSignInRequest",
        "type": "object"
      },
      "PreferredChannel": {
        "enum": [
          "whatsapp",
          "sms",
          "email",
          "auto"
        ],
        "title": "PreferredChannel",
        "type": "string"
      },
      "PreviewImage": {
        "description": "The picture a link to this event unfurls with (design D2, spec link-preview).\n\nStored in a design's `config` rather than in its `assets` on purpose. `assets` is keyed\nby uploaded filename and is the rewrite table `card_html.sanitise` consults, so anything\nin it is by construction something the card document may point at. A preview image is\nnot \u2014 it is never referenced by the card, and putting it there would let a companion\nfile named `og.png` claim the role by accident.",
        "properties": {
          "byte_size": {
            "title": "Byte Size",
            "type": "integer"
          },
          "content_type": {
            "title": "Content Type",
            "type": "string"
          },
          "height": {
            "title": "Height",
            "type": "integer"
          },
          "url": {
            "title": "Url",
            "type": "string"
          },
          "width": {
            "title": "Width",
            "type": "integer"
          }
        },
        "required": [
          "url",
          "width",
          "height",
          "byte_size",
          "content_type"
        ],
        "title": "PreviewImage",
        "type": "object"
      },
      "PreviewImageRead": {
        "description": "Absolute URL and the dimensions the meta tags declare.",
        "properties": {
          "height": {
            "title": "Height",
            "type": "integer"
          },
          "url": {
            "title": "Url",
            "type": "string"
          },
          "width": {
            "title": "Width",
            "type": "integer"
          }
        },
        "required": [
          "url",
          "width",
          "height"
        ],
        "title": "PreviewImageRead",
        "type": "object"
      },
      "ReminderAudience": {
        "enum": [
          "accepted",
          "pending",
          "all"
        ],
        "title": "ReminderAudience",
        "type": "string"
      },
      "ReminderScheduleWrite": {
        "properties": {
          "audience": {
            "$ref": "#/components/schemas/ReminderAudience",
            "default": "accepted"
          },
          "is_enabled": {
            "default": true,
            "title": "Is Enabled",
            "type": "boolean"
          },
          "offset_days": {
            "maximum": 90.0,
            "minimum": 0.0,
            "title": "Offset Days",
            "type": "integer"
          },
          "send_at_local_time": {
            "default": "10:00",
            "title": "Send At Local Time",
            "type": "string"
          }
        },
        "required": [
          "offset_days"
        ],
        "title": "ReminderScheduleWrite",
        "type": "object"
      },
      "RetryResult": {
        "properties": {
          "retried": {
            "title": "Retried",
            "type": "integer"
          }
        },
        "required": [
          "retried"
        ],
        "title": "RetryResult",
        "type": "object"
      },
      "RsvpAcceptRequest": {
        "description": "Accept from a tokenized invitation (FR-2.2).",
        "properties": {
          "adults": {
            "anyOf": [
              {
                "minimum": 0.0,
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Adults"
          },
          "children": {
            "anyOf": [
              {
                "minimum": 0.0,
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Children"
          },
          "dietary_notes": {
            "anyOf": [
              {
                "maxLength": 1000,
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Dietary Notes"
          },
          "email": {
            "anyOf": [
              {
                "format": "email",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Email"
          },
          "message_to_couple": {
            "anyOf": [
              {
                "maxLength": 500,
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Message To Couple"
          },
          "name": {
            "maxLength": 200,
            "minLength": 1,
            "title": "Name",
            "type": "string"
          },
          "party_size": {
            "default": 1,
            "minimum": 1.0,
            "title": "Party Size",
            "type": "integer"
          },
          "phone": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Phone"
          }
        },
        "required": [
          "name"
        ],
        "title": "RsvpAcceptRequest",
        "type": "object"
      },
      "RsvpCancelRequest": {
        "description": "Cancellation always requires an explicit confirmation flag, so a link-preview\ncrawler cannot produce a valid body by accident (FR-3.2, FR-3.3).",
        "properties": {
          "confirm": {
            "title": "Confirm",
            "type": "boolean"
          },
          "reason": {
            "anyOf": [
              {
                "maxLength": 500,
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Reason"
          }
        },
        "required": [
          "confirm"
        ],
        "title": "RsvpCancelRequest",
        "type": "object"
      },
      "RsvpConfirmation": {
        "description": "What the success screen renders (FR-2.7).",
        "properties": {
          "calendar_url": {
            "title": "Calendar Url",
            "type": "string"
          },
          "cancel_url": {
            "title": "Cancel Url",
            "type": "string"
          },
          "confirmation_email_queued": {
            "title": "Confirmation Email Queued",
            "type": "boolean"
          },
          "event_title_bn": {
            "title": "Event Title Bn",
            "type": "string"
          },
          "event_title_en": {
            "title": "Event Title En",
            "type": "string"
          },
          "invitation_id": {
            "format": "uuid",
            "title": "Invitation Id",
            "type": "string"
          },
          "party_size": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Party Size"
          },
          "response": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/RsvpResponse"
              },
              {
                "type": "null"
              }
            ]
          },
          "starts_at": {
            "format": "date-time",
            "title": "Starts At",
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/InvitationStatus"
          },
          "venue_name": {
            "title": "Venue Name",
            "type": "string"
          }
        },
        "required": [
          "invitation_id",
          "status",
          "response",
          "party_size",
          "event_title_en",
          "event_title_bn",
          "starts_at",
          "venue_name",
          "cancel_url",
          "calendar_url",
          "confirmation_email_queued"
        ],
        "title": "RsvpConfirmation",
        "type": "object"
      },
      "RsvpDeclineRequest": {
        "description": "Declining needs no form (FR-2.6).",
        "properties": {
          "reason": {
            "anyOf": [
              {
                "maxLength": 500,
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Reason"
          }
        },
        "title": "RsvpDeclineRequest",
        "type": "object"
      },
      "RsvpResponse": {
        "enum": [
          "accepted",
          "declined",
          "cancelled"
        ],
        "title": "RsvpResponse",
        "type": "string"
      },
      "SendBatchRequest": {
        "properties": {
          "batch_id": {
            "format": "uuid",
            "title": "Batch Id",
            "type": "string"
          },
          "confirmed_quiet_hours": {
            "default": false,
            "title": "Confirmed Quiet Hours",
            "type": "boolean"
          },
          "guest_ids": {
            "items": {
              "format": "uuid",
              "type": "string"
            },
            "maxItems": 2000,
            "minItems": 1,
            "title": "Guest Ids",
            "type": "array"
          },
          "panes": {
            "items": {
              "$ref": "#/components/schemas/BulkPaneWrite"
            },
            "title": "Panes",
            "type": "array"
          }
        },
        "required": [
          "batch_id",
          "guest_ids",
          "panes"
        ],
        "title": "SendBatchRequest",
        "type": "object"
      },
      "SendBatchResult": {
        "properties": {
          "already_recorded": {
            "title": "Already Recorded",
            "type": "boolean"
          },
          "batch_id": {
            "format": "uuid",
            "title": "Batch Id",
            "type": "string"
          },
          "dry_run": {
            "title": "Dry Run",
            "type": "boolean"
          },
          "exclusions": {
            "items": {
              "$ref": "#/components/schemas/ExclusionRead"
            },
            "title": "Exclusions",
            "type": "array"
          },
          "missing": {
            "title": "Missing",
            "type": "integer"
          },
          "queued": {
            "title": "Queued",
            "type": "integer"
          }
        },
        "required": [
          "batch_id",
          "queued",
          "already_recorded",
          "exclusions",
          "missing",
          "dry_run"
        ],
        "title": "SendBatchResult",
        "type": "object"
      },
      "SendInvitationRequest": {
        "properties": {
          "body": {
            "title": "Body",
            "type": "string"
          },
          "confirmed_quiet_hours": {
            "default": false,
            "title": "Confirmed Quiet Hours",
            "type": "boolean"
          },
          "footer": {
            "title": "Footer",
            "type": "string"
          },
          "header": {
            "title": "Header",
            "type": "string"
          },
          "subject": {
            "title": "Subject",
            "type": "string"
          }
        },
        "required": [
          "subject",
          "header",
          "body",
          "footer"
        ],
        "title": "SendInvitationRequest",
        "type": "object"
      },
      "SendInvitationResult": {
        "properties": {
          "dry_run": {
            "title": "Dry Run",
            "type": "boolean"
          },
          "error_message": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Error Message"
          },
          "job_id": {
            "format": "uuid",
            "title": "Job Id",
            "type": "string"
          },
          "sent_at": {
            "anyOf": [
              {
                "format": "date-time",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Sent At"
          },
          "skip_reason": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Skip Reason"
          },
          "status": {
            "title": "Status",
            "type": "string"
          }
        },
        "required": [
          "job_id",
          "status",
          "sent_at",
          "error_message",
          "skip_reason",
          "dry_run"
        ],
        "title": "SendInvitationResult",
        "type": "object"
      },
      "SendPreview": {
        "properties": {
          "deliverable": {
            "title": "Deliverable",
            "type": "integer"
          },
          "missing_placeholders": {
            "items": {
              "type": "string"
            },
            "title": "Missing Placeholders",
            "type": "array"
          },
          "no_email": {
            "title": "No Email",
            "type": "integer"
          },
          "opted_out": {
            "title": "Opted Out",
            "type": "integer"
          },
          "sample_body": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Sample Body"
          },
          "sample_subject": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Sample Subject"
          },
          "total_matched": {
            "title": "Total Matched",
            "type": "integer"
          }
        },
        "required": [
          "total_matched",
          "deliverable",
          "no_email",
          "opted_out",
          "sample_subject",
          "sample_body",
          "missing_placeholders"
        ],
        "title": "SendPreview",
        "type": "object"
      },
      "SendPreviewRequest": {
        "properties": {
          "audience": {
            "$ref": "#/components/schemas/ReminderAudience",
            "default": "all"
          },
          "event": {
            "$ref": "#/components/schemas/EventType"
          },
          "purpose": {
            "$ref": "#/components/schemas/TemplatePurpose",
            "default": "invite"
          },
          "tag": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Tag"
          }
        },
        "required": [
          "event"
        ],
        "title": "SendPreviewRequest",
        "type": "object"
      },
      "SendRequest": {
        "properties": {
          "audience": {
            "$ref": "#/components/schemas/ReminderAudience",
            "default": "all"
          },
          "event": {
            "$ref": "#/components/schemas/EventType"
          },
          "purpose": {
            "$ref": "#/components/schemas/TemplatePurpose",
            "default": "invite"
          },
          "scheduled_for": {
            "anyOf": [
              {
                "format": "date-time",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Scheduled For"
          },
          "tag": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Tag"
          }
        },
        "required": [
          "event"
        ],
        "title": "SendRequest",
        "type": "object"
      },
      "SendResult": {
        "properties": {
          "already_queued": {
            "title": "Already Queued",
            "type": "integer"
          },
          "queued": {
            "title": "Queued",
            "type": "integer"
          },
          "skipped_no_email": {
            "title": "Skipped No Email",
            "type": "integer"
          }
        },
        "required": [
          "queued",
          "skipped_no_email",
          "already_queued"
        ],
        "title": "SendResult",
        "type": "object"
      },
      "SessionRead": {
        "description": "The signed-in admin's own account. Also serves the profile screen (task 7.1).\n\nOne payload rather than two, because a second endpoint returning the same five fields\nabout the same person is a second thing to keep in step.",
        "properties": {
          "auth_method": {
            "$ref": "#/components/schemas/AuthMethod"
          },
          "email": {
            "title": "Email",
            "type": "string"
          },
          "must_change_password": {
            "title": "Must Change Password",
            "type": "boolean"
          },
          "name": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Name"
          },
          "permissions": {
            "items": {
              "type": "string"
            },
            "title": "Permissions",
            "type": "array"
          },
          "role": {
            "$ref": "#/components/schemas/AdminRole"
          },
          "username": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Username"
          }
        },
        "required": [
          "email",
          "name",
          "role",
          "permissions",
          "username",
          "auth_method",
          "must_change_password"
        ],
        "title": "SessionRead",
        "type": "object"
      },
      "TemplatePurpose": {
        "enum": [
          "invite",
          "reminder_15",
          "reminder_7",
          "reminder_2",
          "thank_you",
          "cancel_confirm",
          "custom"
        ],
        "title": "TemplatePurpose",
        "type": "string"
      },
      "TemporaryPasswordRead": {
        "description": "The one response in this system that carries a password. Shown once, never stored.",
        "properties": {
          "expires_at": {
            "format": "date-time",
            "title": "Expires At",
            "type": "string"
          },
          "password": {
            "title": "Password",
            "type": "string"
          }
        },
        "required": [
          "password",
          "expires_at"
        ],
        "title": "TemporaryPasswordRead",
        "type": "object"
      },
      "TokenInvitationRead": {
        "description": "Payload for `/i/{token}`.",
        "properties": {
          "can_cancel": {
            "title": "Can Cancel",
            "type": "boolean"
          },
          "card": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/CardRead"
              },
              {
                "type": "null"
              }
            ]
          },
          "couple": {
            "$ref": "#/components/schemas/CoupleRead"
          },
          "current_rsvp": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/CurrentRsvpRead"
              },
              {
                "type": "null"
              }
            ]
          },
          "event": {
            "$ref": "#/components/schemas/EventRead"
          },
          "greeting": {
            "$ref": "#/components/schemas/GreetingRead"
          },
          "guest": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/GuestPrefill"
              },
              {
                "type": "null"
              }
            ]
          },
          "invitation_id": {
            "format": "uuid",
            "title": "Invitation Id",
            "type": "string"
          },
          "max_guests": {
            "title": "Max Guests",
            "type": "integer"
          },
          "preview": {
            "$ref": "#/components/schemas/LinkPreviewRead"
          },
          "rsvp_open": {
            "title": "Rsvp Open",
            "type": "boolean"
          },
          "short_code": {
            "title": "Short Code",
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/InvitationStatus"
          }
        },
        "required": [
          "invitation_id",
          "status",
          "max_guests",
          "short_code",
          "event",
          "couple",
          "guest",
          "greeting",
          "card",
          "preview",
          "current_rsvp",
          "rsvp_open",
          "can_cancel"
        ],
        "title": "TokenInvitationRead",
        "type": "object"
      },
      "TrendPoint": {
        "properties": {
          "accepts": {
            "title": "Accepts",
            "type": "integer"
          },
          "day": {
            "title": "Day",
            "type": "string"
          }
        },
        "required": [
          "day",
          "accepts"
        ],
        "title": "TrendPoint",
        "type": "object"
      },
      "UnsubscribeRead": {
        "description": "What the confirmation page needs in order to state what it is about to do.\n\nThe event titles are the point of this payload \u2014 a page that says \"stop receiving\nemails\" without naming which list is exactly the misleading copy the spec forbids.",
        "properties": {
          "already_unsubscribed": {
            "title": "Already Unsubscribed",
            "type": "boolean"
          },
          "event_title_bn": {
            "title": "Event Title Bn",
            "type": "string"
          },
          "event_title_en": {
            "title": "Event Title En",
            "type": "string"
          },
          "guest_name": {
            "title": "Guest Name",
            "type": "string"
          }
        },
        "required": [
          "guest_name",
          "event_title_en",
          "event_title_bn",
          "already_unsubscribed"
        ],
        "title": "UnsubscribeRead",
        "type": "object"
      },
      "UnsubscribeRequest": {
        "description": "Explicit confirmation, so a prefetch or scanner cannot carry the intent.",
        "properties": {
          "confirm": {
            "default": false,
            "title": "Confirm",
            "type": "boolean"
          }
        },
        "title": "UnsubscribeRequest",
        "type": "object"
      },
      "ValidationError": {
        "properties": {
          "ctx": {
            "title": "Context",
            "type": "object"
          },
          "input": {
            "title": "Input"
          },
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "title": "Location",
            "type": "array"
          },
          "msg": {
            "title": "Message",
            "type": "string"
          },
          "type": {
            "title": "Error Type",
            "type": "string"
          }
        },
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError",
        "type": "object"
      },
      "WavePreview": {
        "properties": {
          "event_slug": {
            "title": "Event Slug",
            "type": "string"
          },
          "no_email": {
            "title": "No Email",
            "type": "integer"
          },
          "offset_days": {
            "title": "Offset Days",
            "type": "integer"
          },
          "recipients": {
            "title": "Recipients",
            "type": "integer"
          },
          "send_at": {
            "format": "date-time",
            "title": "Send At",
            "type": "string"
          },
          "skipped_past": {
            "title": "Skipped Past",
            "type": "boolean"
          }
        },
        "required": [
          "event_slug",
          "offset_days",
          "send_at",
          "recipients",
          "no_email",
          "skipped_past"
        ],
        "title": "WavePreview",
        "type": "object"
      },
      "WeddingRead": {
        "properties": {
          "bride_name": {
            "title": "Bride Name",
            "type": "string"
          },
          "created_at": {
            "format": "date-time",
            "title": "Created At",
            "type": "string"
          },
          "default_locale": {
            "$ref": "#/components/schemas/Locale"
          },
          "groom_name": {
            "title": "Groom Name",
            "type": "string"
          },
          "host_contact_phone": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Host Contact Phone"
          },
          "host_email": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Host Email"
          },
          "id": {
            "format": "uuid",
            "title": "Id",
            "type": "string"
          },
          "invitation_messages": {
            "additionalProperties": {
              "additionalProperties": {
                "type": "string"
              },
              "type": "object"
            },
            "title": "Invitation Messages",
            "type": "object"
          },
          "slug": {
            "title": "Slug",
            "type": "string"
          },
          "timezone": {
            "title": "Timezone",
            "type": "string"
          }
        },
        "required": [
          "id",
          "bride_name",
          "groom_name",
          "slug",
          "default_locale",
          "timezone",
          "host_contact_phone",
          "host_email",
          "invitation_messages",
          "created_at"
        ],
        "title": "WeddingRead",
        "type": "object"
      },
      "WeddingSettingsWrite": {
        "description": "The settings a host may change from the admin. Currently just the reply address.\n\n`EmailStr` rather than a loose string: this value goes into a `Reply-To` header, and a\nmalformed one is rejected by the provider at send time \u2014 which would surface as a failed\ninvitation hours later instead of as a validation error while they were typing it.",
        "properties": {
          "host_email": {
            "anyOf": [
              {
                "format": "email",
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Host Email"
          }
        },
        "title": "WeddingSettingsWrite",
        "type": "object"
      }
    }
  },
  "info": {
    "title": "Wedding RSVP API",
    "version": "0.1.0"
  },
  "openapi": "3.1.0",
  "paths": {
    "/api": {
      "get": {
        "operationId": "root_api_get",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "additionalProperties": {
                    "type": "string"
                  },
                  "title": "Response Root Api Get",
                  "type": "object"
                }
              }
            },
            "description": "Successful Response"
          }
        },
        "summary": "Root"
      }
    },
    "/api/admin/card-designs/{design_id}": {
      "delete": {
        "description": "Delete a draft. A published design must be unpublished first.\n\nIts files are left on disk. They are content-addressed, so another version of the same\ncard may well reference the identical bytes \u2014 deleting them here would blank a live card\nto reclaim a few kilobytes.",
        "operationId": "delete_design_api_admin_card_designs__design_id__delete",
        "parameters": [
          {
            "in": "path",
            "name": "design_id",
            "required": true,
            "schema": {
              "format": "uuid",
              "title": "Design Id",
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Delete Design",
        "tags": [
          "admin"
        ]
      }
    },
    "/api/admin/card-designs/{design_id}/publish": {
      "post": {
        "description": "Make this design the one guests see, demoting whatever was published before.\n\nThe demotion happens first and is flushed before the promotion, because the partial\nunique index does not care that both writes are in one transaction \u2014 two published rows\nat any point inside it is a violation.",
        "operationId": "publish_design_api_admin_card_designs__design_id__publish_post",
        "parameters": [
          {
            "in": "path",
            "name": "design_id",
            "required": true,
            "schema": {
              "format": "uuid",
              "title": "Design Id",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CardDesignRead"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Publish Design",
        "tags": [
          "admin"
        ]
      }
    },
    "/api/admin/card-designs/{design_id}/unpublish": {
      "post": {
        "description": "Take the card off the invitation. The page falls back to its plain composition.",
        "operationId": "unpublish_design_api_admin_card_designs__design_id__unpublish_post",
        "parameters": [
          {
            "in": "path",
            "name": "design_id",
            "required": true,
            "schema": {
              "format": "uuid",
              "title": "Design Id",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CardDesignRead"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Unpublish Design",
        "tags": [
          "admin"
        ]
      }
    },
    "/api/admin/events": {
      "get": {
        "operationId": "list_events_api_admin_events_get",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/AdminEventRead"
                  },
                  "title": "Response List Events Api Admin Events Get",
                  "type": "array"
                }
              }
            },
            "description": "Successful Response"
          }
        },
        "summary": "List Events",
        "tags": [
          "admin"
        ]
      },
      "post": {
        "description": "Create an event of one of the three ceremony types.\n\nThere is deliberately no uniqueness on (wedding, type): a family may hold two receptions,\nand the constraint that used to forbid that encoded \"one wedding, exactly three\nceremonies\" into the schema (design D12).",
        "operationId": "create_event_api_admin_events_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EventCreate"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminEventRead"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Create Event",
        "tags": [
          "admin"
        ]
      }
    },
    "/api/admin/events/{event_id}": {
      "delete": {
        "description": "Delete an event and everything under it.\n\nGuests belong to one event (design D11), so this cascades to their records, invitations\nand RSVPs. The confirmation the interface shows is built from `delete-impact` above; the\ncounts are captured into the audit log here because after the cascade nothing can be\ncounted retrospectively.",
        "operationId": "delete_event_api_admin_events__event_id__delete",
        "parameters": [
          {
            "in": "path",
            "name": "event_id",
            "required": true,
            "schema": {
              "format": "uuid",
              "title": "Event Id",
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Delete Event",
        "tags": [
          "admin"
        ]
      },
      "patch": {
        "operationId": "update_event_api_admin_events__event_id__patch",
        "parameters": [
          {
            "in": "path",
            "name": "event_id",
            "required": true,
            "schema": {
              "format": "uuid",
              "title": "Event Id",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EventUpdate"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EventUpdateResult"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Update Event",
        "tags": [
          "admin"
        ]
      }
    },
    "/api/admin/events/{event_id}/broadcast-date-change": {
      "post": {
        "operationId": "broadcast_date_change_api_admin_events__event_id__broadcast_date_change_post",
        "parameters": [
          {
            "in": "path",
            "name": "event_id",
            "required": true,
            "schema": {
              "format": "uuid",
              "title": "Event Id",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DateChangeBroadcast"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BroadcastResult"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Broadcast Date Change",
        "tags": [
          "admin"
        ]
      }
    },
    "/api/admin/events/{event_id}/card-designs": {
      "get": {
        "description": "Newest first, so the current version and the one to roll back to are both at hand.",
        "operationId": "list_designs_api_admin_events__event_id__card_designs_get",
        "parameters": [
          {
            "in": "path",
            "name": "event_id",
            "required": true,
            "schema": {
              "format": "uuid",
              "title": "Event Id",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/CardDesignRead"
                  },
                  "title": "Response List Designs Api Admin Events  Event Id  Card Designs Get",
                  "type": "array"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "List Designs",
        "tags": [
          "admin"
        ]
      },
      "post": {
        "description": "Upload a card and its companion files together, as one draft.\n\nTogether and not separately: the document's references can only be rewritten against\nfiles that are already stored, so an upload split in two would need an intermediate\nstate where a design exists with references pointing nowhere.",
        "operationId": "upload_design_api_admin_events__event_id__card_designs_post",
        "parameters": [
          {
            "in": "path",
            "name": "event_id",
            "required": true,
            "schema": {
              "format": "uuid",
              "title": "Event Id",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/Body_upload_design_api_admin_events__event_id__card_designs_post"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CardDesignRead"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Upload Design",
        "tags": [
          "admin"
        ]
      }
    },
    "/api/admin/events/{event_id}/delete-impact": {
      "get": {
        "description": "Counts for the confirmation dialog. Read-only and safe to call repeatedly.",
        "operationId": "event_delete_impact_api_admin_events__event_id__delete_impact_get",
        "parameters": [
          {
            "in": "path",
            "name": "event_id",
            "required": true,
            "schema": {
              "format": "uuid",
              "title": "Event Id",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EventDeleteImpact"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Event Delete Impact",
        "tags": [
          "admin"
        ]
      }
    },
    "/api/admin/events/{event_id}/guest-ids": {
      "get": {
        "description": "Resolve \"select all N matching\" into the ids it means (D2).\n\nThe selection is resolved here, once, rather than re-evaluated at send time: a\nfilter-shaped selection can grow between the count on the button and the messages going\nout \u2014 an import finishing in another tab is enough \u2014 and the set the admin approved is\nthe set that must be sent.",
        "operationId": "list_event_guest_ids_api_admin_events__event_id__guest_ids_get",
        "parameters": [
          {
            "in": "path",
            "name": "event_id",
            "required": true,
            "schema": {
              "format": "uuid",
              "title": "Event Id",
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "search",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Search"
            }
          },
          {
            "in": "query",
            "name": "invitation_status",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "$ref": "#/components/schemas/InvitationStatus"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Invitation Status"
            }
          },
          {
            "in": "query",
            "name": "side",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "$ref": "#/components/schemas/GuestSide"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Side"
            }
          },
          {
            "in": "query",
            "name": "tag",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Tag"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GuestIdsRead"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "List Event Guest Ids",
        "tags": [
          "admin"
        ]
      }
    },
    "/api/admin/events/{event_id}/guests": {
      "get": {
        "description": "One event's guest list \u2014 the list a host actually works from (spec event-management).",
        "operationId": "list_event_guests_api_admin_events__event_id__guests_get",
        "parameters": [
          {
            "in": "path",
            "name": "event_id",
            "required": true,
            "schema": {
              "format": "uuid",
              "title": "Event Id",
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "search",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Search"
            }
          },
          {
            "in": "query",
            "name": "invitation_status",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "$ref": "#/components/schemas/InvitationStatus"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Invitation Status"
            }
          },
          {
            "in": "query",
            "name": "side",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "$ref": "#/components/schemas/GuestSide"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Side"
            }
          },
          {
            "in": "query",
            "name": "tag",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Tag"
            }
          },
          {
            "in": "query",
            "name": "page",
            "required": false,
            "schema": {
              "default": 1,
              "minimum": 1,
              "title": "Page",
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "page_size",
            "required": false,
            "schema": {
              "default": 50,
              "maximum": 200,
              "minimum": 1,
              "title": "Page Size",
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GuestPage"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "List Event Guests",
        "tags": [
          "admin"
        ]
      },
      "post": {
        "description": "Add a guest to one event's list, with their invitation and token.",
        "operationId": "create_guest_api_admin_events__event_id__guests_post",
        "parameters": [
          {
            "in": "path",
            "name": "event_id",
            "required": true,
            "schema": {
              "format": "uuid",
              "title": "Event Id",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GuestWrite"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GuestRead"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Create Guest",
        "tags": [
          "admin"
        ]
      }
    },
    "/api/admin/events/{event_id}/guests/import": {
      "post": {
        "description": "Import a sheet into one event's guest list.\n\nThe sheet's `events` column is ignored: the destination comes from the route, so a file\ncannot quietly place guests on a list the admin was not looking at (design D11).",
        "operationId": "commit_import_api_admin_events__event_id__guests_import_post",
        "parameters": [
          {
            "in": "path",
            "name": "event_id",
            "required": true,
            "schema": {
              "format": "uuid",
              "title": "Event Id",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/Body_commit_import_api_admin_events__event_id__guests_import_post"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ImportResult"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Commit Import",
        "tags": [
          "admin"
        ]
      }
    },
    "/api/admin/events/{event_id}/guests/import/preview": {
      "post": {
        "description": "Dry run: first 10 rows plus every error, before anything is written (FR-4.8).\n\nDuplicates are judged against this event's list only (design D11) \u2014 a phone already on\nanother event's list is a different guest record, not a re-import.",
        "operationId": "preview_import_api_admin_events__event_id__guests_import_preview_post",
        "parameters": [
          {
            "in": "path",
            "name": "event_id",
            "required": true,
            "schema": {
              "format": "uuid",
              "title": "Event Id",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/Body_preview_import_api_admin_events__event_id__guests_import_preview_post"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ImportPreview"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Preview Import",
        "tags": [
          "admin"
        ]
      }
    },
    "/api/admin/events/{event_id}/invitations/compose": {
      "post": {
        "description": "The two messages this selection would receive, ready to be edited (D1, D10, D11).",
        "operationId": "compose_bulk_invitation_api_admin_events__event_id__invitations_compose_post",
        "parameters": [
          {
            "in": "path",
            "name": "event_id",
            "required": true,
            "schema": {
              "format": "uuid",
              "title": "Event Id",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BulkComposeRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BulkComposeRead"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Compose Bulk Invitation",
        "tags": [
          "admin"
        ]
      }
    },
    "/api/admin/events/{event_id}/invitations/send-batch": {
      "post": {
        "description": "Record one batched send and start it, without waiting for it to finish (D6).",
        "operationId": "send_invitation_batch_api_admin_events__event_id__invitations_send_batch_post",
        "parameters": [
          {
            "in": "path",
            "name": "event_id",
            "required": true,
            "schema": {
              "format": "uuid",
              "title": "Event Id",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SendBatchRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SendBatchResult"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Send Invitation Batch",
        "tags": [
          "admin"
        ]
      }
    },
    "/api/admin/events/{event_id}/transfer": {
      "post": {
        "description": "Move an event, and everything under it, to another active account.\n\nGuests, invitations, the card design and the message history all follow without being\ntouched, because every one of them is reachable through the event rather than owned\nseparately (design D1).",
        "operationId": "transfer_event_ownership_api_admin_events__event_id__transfer_post",
        "parameters": [
          {
            "in": "path",
            "name": "event_id",
            "required": true,
            "schema": {
              "format": "uuid",
              "title": "Event Id",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OwnershipTransfer"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminUserRead"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Transfer Event Ownership",
        "tags": [
          "admin-users"
        ]
      }
    },
    "/api/admin/export": {
      "get": {
        "description": "Filtered, streamed, BOM-prefixed export. Always audited (FR-5.7).",
        "operationId": "export_csv_api_admin_export_get",
        "parameters": [
          {
            "in": "query",
            "name": "event_id",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "format": "uuid",
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Event Id"
            }
          },
          {
            "in": "query",
            "name": "invitation_status",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "$ref": "#/components/schemas/InvitationStatus"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Invitation Status"
            }
          },
          {
            "in": "query",
            "name": "preset",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "pattern": "^(full|caterer)$",
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Preset"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {}
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Export Csv",
        "tags": [
          "admin"
        ]
      }
    },
    "/api/admin/guests": {
      "get": {
        "description": "Search across every event's list (FR-4.5).\n\nKept alongside the event-scoped route because export and global search still need it.\nThe count it reports is of guest *records*: a person attending three ceremonies appears\nthree times, which is what event-scoped guests mean (design D11).",
        "operationId": "list_guests_api_admin_guests_get",
        "parameters": [
          {
            "in": "query",
            "name": "search",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Search"
            }
          },
          {
            "in": "query",
            "name": "event_id",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "format": "uuid",
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Event Id"
            }
          },
          {
            "in": "query",
            "name": "invitation_status",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "$ref": "#/components/schemas/InvitationStatus"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Invitation Status"
            }
          },
          {
            "in": "query",
            "name": "side",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "$ref": "#/components/schemas/GuestSide"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Side"
            }
          },
          {
            "in": "query",
            "name": "tag",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Tag"
            }
          },
          {
            "in": "query",
            "name": "page",
            "required": false,
            "schema": {
              "default": 1,
              "minimum": 1,
              "title": "Page",
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "page_size",
            "required": false,
            "schema": {
              "default": 50,
              "maximum": 200,
              "minimum": 1,
              "title": "Page Size",
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GuestPage"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "List Guests",
        "tags": [
          "admin"
        ]
      }
    },
    "/api/admin/guests/{guest_id}": {
      "delete": {
        "description": "Soft delete \u2014 Super Admin only. History survives for the audit trail.",
        "operationId": "delete_guest_api_admin_guests__guest_id__delete",
        "parameters": [
          {
            "in": "path",
            "name": "guest_id",
            "required": true,
            "schema": {
              "format": "uuid",
              "title": "Guest Id",
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Delete Guest",
        "tags": [
          "admin"
        ]
      },
      "get": {
        "operationId": "read_guest_api_admin_guests__guest_id__get",
        "parameters": [
          {
            "in": "path",
            "name": "guest_id",
            "required": true,
            "schema": {
              "format": "uuid",
              "title": "Guest Id",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GuestRead"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Read Guest",
        "tags": [
          "admin"
        ]
      },
      "patch": {
        "operationId": "update_guest_api_admin_guests__guest_id__patch",
        "parameters": [
          {
            "in": "path",
            "name": "guest_id",
            "required": true,
            "schema": {
              "format": "uuid",
              "title": "Guest Id",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GuestWrite"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GuestRead"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Update Guest",
        "tags": [
          "admin"
        ]
      }
    },
    "/api/admin/invitations/{invitation_id}/link-copied": {
      "post": {
        "description": "Record that an admin took this invitation's link to deliver by hand (design D8).\n\nWrites an audit entry and nothing else: no `message_job`, no `message_log`, no\n`last_sent_at`. Copying a link is not a send, and recording it as one would put a\ndelivery in the host's history that never happened.\n\nWorth being honest about what this does and does not prove. The compose panel already\nreceived `invite_url` when it loaded, so the token was disclosed before this fires \u2014\nwhat is recorded here is the admin's *intent* to hand the link out, not the moment of\ndisclosure. That is still the useful question afterwards (\"who gave this out, and\nwhen\"), but it is not a containment boundary and should not be mistaken for one.\n\nDeliberately permitted for a suppressed guest. Suppression governs what the system\nsends, not what a host may hand someone in person; the panel states the suppression\nabove the control so the admin decides knowingly.",
        "operationId": "record_link_copy_api_admin_invitations__invitation_id__link_copied_post",
        "parameters": [
          {
            "in": "path",
            "name": "invitation_id",
            "required": true,
            "schema": {
              "format": "uuid",
              "title": "Invitation Id",
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Record Link Copy",
        "tags": [
          "admin"
        ]
      }
    },
    "/api/admin/invitations/{invitation_id}/message": {
      "get": {
        "description": "The message this guest would receive, ready to be edited (D4).",
        "operationId": "compose_invitation_message_api_admin_invitations__invitation_id__message_get",
        "parameters": [
          {
            "in": "path",
            "name": "invitation_id",
            "required": true,
            "schema": {
              "format": "uuid",
              "title": "Invitation Id",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ComposedMessageRead"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Compose Invitation Message",
        "tags": [
          "admin"
        ]
      }
    },
    "/api/admin/invitations/{invitation_id}/rsvp": {
      "post": {
        "operationId": "override_rsvp_api_admin_invitations__invitation_id__rsvp_post",
        "parameters": [
          {
            "in": "path",
            "name": "invitation_id",
            "required": true,
            "schema": {
              "format": "uuid",
              "title": "Invitation Id",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ManualRsvpRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InvitationSummary"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Override Rsvp",
        "tags": [
          "admin"
        ]
      }
    },
    "/api/admin/invitations/{invitation_id}/send": {
      "post": {
        "description": "Send one hand-edited invitation email, and report what actually happened.",
        "operationId": "send_invitation_email_api_admin_invitations__invitation_id__send_post",
        "parameters": [
          {
            "in": "path",
            "name": "invitation_id",
            "required": true,
            "schema": {
              "format": "uuid",
              "title": "Invitation Id",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SendInvitationRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SendInvitationResult"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Send Invitation Email",
        "tags": [
          "admin"
        ]
      }
    },
    "/api/admin/messages": {
      "get": {
        "operationId": "read_message_log_api_admin_messages_get",
        "parameters": [
          {
            "in": "query",
            "name": "job_status",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "$ref": "#/components/schemas/MessageJobStatus"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Job Status"
            }
          },
          {
            "in": "query",
            "name": "page",
            "required": false,
            "schema": {
              "default": 1,
              "minimum": 1,
              "title": "Page",
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "page_size",
            "required": false,
            "schema": {
              "default": 50,
              "maximum": 200,
              "minimum": 1,
              "title": "Page Size",
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MessageLogPage"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Read Message Log",
        "tags": [
          "admin"
        ]
      }
    },
    "/api/admin/messages/retry": {
      "post": {
        "description": "Re-queue failed jobs (FR-4.10). Attempts reset so the backoff starts fresh.",
        "operationId": "retry_failed_api_admin_messages_retry_post",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RetryResult"
                }
              }
            },
            "description": "Successful Response"
          }
        },
        "summary": "Retry Failed",
        "tags": [
          "admin"
        ]
      }
    },
    "/api/admin/qr/guest/{invitation_id}": {
      "get": {
        "description": "Per-guest QR encoding /i/{token} for personalised printed cards (FR-7.3).\n\nSVG is the print format. PNG exists because the common case is not printing at all \u2014\nit is an admin sending one guest their code over WhatsApp, and WhatsApp will not\nrender an SVG.",
        "operationId": "guest_qr_api_admin_qr_guest__invitation_id__get",
        "parameters": [
          {
            "in": "path",
            "name": "invitation_id",
            "required": true,
            "schema": {
              "format": "uuid",
              "title": "Invitation Id",
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "format",
            "required": false,
            "schema": {
              "default": "svg",
              "pattern": "^(svg|png)$",
              "title": "Format",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {}
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Guest Qr",
        "tags": [
          "admin"
        ]
      }
    },
    "/api/admin/qr/{event_slug}": {
      "get": {
        "description": "Per-event open-link QR at error-correction H (spec qr-codes FR-7.1, FR-7.2).\n\nLevel H tolerates roughly 30% damage, which is what lets a decorative print \u2014 or a\ncard that has been in someone's pocket \u2014 still scan.",
        "operationId": "event_qr_api_admin_qr__event_slug__get",
        "parameters": [
          {
            "in": "path",
            "name": "event_slug",
            "required": true,
            "schema": {
              "title": "Event Slug",
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "format",
            "required": false,
            "schema": {
              "default": "svg",
              "pattern": "^(svg|png)$",
              "title": "Format",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {}
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Event Qr",
        "tags": [
          "admin"
        ]
      }
    },
    "/api/admin/reminders/preview": {
      "get": {
        "description": "Upcoming waves with exact dates and recipient counts (FR-4.11), without queueing.",
        "operationId": "preview_reminder_waves_api_admin_reminders_preview_get",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/WavePreview"
                  },
                  "title": "Response Preview Reminder Waves Api Admin Reminders Preview Get",
                  "type": "array"
                }
              }
            },
            "description": "Successful Response"
          }
        },
        "summary": "Preview Reminder Waves",
        "tags": [
          "admin"
        ]
      }
    },
    "/api/admin/reminders/{schedule_id}": {
      "patch": {
        "operationId": "update_reminder_schedule_api_admin_reminders__schedule_id__patch",
        "parameters": [
          {
            "in": "path",
            "name": "schedule_id",
            "required": true,
            "schema": {
              "format": "uuid",
              "title": "Schedule Id",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReminderScheduleWrite"
              }
            }
          },
          "required": true
        },
        "responses": {
          "204": {
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Update Reminder Schedule",
        "tags": [
          "admin"
        ]
      }
    },
    "/api/admin/send": {
      "post": {
        "operationId": "queue_send_api_admin_send_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SendRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SendResult"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Queue Send",
        "tags": [
          "admin"
        ]
      }
    },
    "/api/admin/send-batches/{batch_id}": {
      "get": {
        "description": "How a batch is getting on (D7).\n\nPolled while anything is still waiting. An unknown batch reads as an empty, finished one\nrather than a 404: a batch is a set of jobs, not a row, so \"no jobs\" is a truthful answer\nand the composer has nothing useful to do with an error here.\n\nA batch id comes from the browser, so one host could poll another's \u2014 and `problems`\ncarries guest *names*. Scoped for that reason: a batch with no jobs the caller may see\nreads exactly like a batch id that was never used, which is the same answer this endpoint\nalready gives for one that does not exist.",
        "operationId": "read_batch_progress_api_admin_send_batches__batch_id__get",
        "parameters": [
          {
            "in": "path",
            "name": "batch_id",
            "required": true,
            "schema": {
              "format": "uuid",
              "title": "Batch Id",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BatchProgressRead"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Read Batch Progress",
        "tags": [
          "admin"
        ]
      }
    },
    "/api/admin/send/preview": {
      "post": {
        "description": "Counts and a rendered sample, before anything is queued (FR-4.9).",
        "operationId": "preview_send_api_admin_send_preview_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SendPreviewRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SendPreview"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Preview Send",
        "tags": [
          "admin"
        ]
      }
    },
    "/api/admin/stats": {
      "get": {
        "operationId": "read_dashboard_stats_api_admin_stats_get",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DashboardStats"
                }
              }
            },
            "description": "Successful Response"
          }
        },
        "summary": "Read Dashboard Stats",
        "tags": [
          "admin"
        ]
      }
    },
    "/api/admin/stats/trend": {
      "get": {
        "description": "Accepts per day (FR-4.4), for the response trend chart.",
        "operationId": "read_response_trend_api_admin_stats_trend_get",
        "parameters": [
          {
            "in": "query",
            "name": "days",
            "required": false,
            "schema": {
              "default": 30,
              "title": "Days",
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/TrendPoint"
                  },
                  "title": "Response Read Response Trend Api Admin Stats Trend Get",
                  "type": "array"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Read Response Trend",
        "tags": [
          "admin"
        ]
      }
    },
    "/api/admin/users": {
      "get": {
        "operationId": "list_admin_users_api_admin_users_get",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/AdminUserRead"
                  },
                  "title": "Response List Admin Users Api Admin Users Get",
                  "type": "array"
                }
              }
            },
            "description": "Successful Response"
          }
        },
        "summary": "List Admin Users",
        "tags": [
          "admin-users"
        ]
      },
      "post": {
        "operationId": "create_admin_user_api_admin_users_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AdminUserCreate"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminUserRead"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Create Admin User",
        "tags": [
          "admin-users"
        ]
      }
    },
    "/api/admin/users/{admin_id}": {
      "delete": {
        "description": "Remove an account outright. Refused while it still owns anything.\n\nThe FK is `ON DELETE RESTRICT`, so the database would refuse this anyway \u2014 but it would\nrefuse with an integrity error, and the super admin deserves to be told how many events\nthey need to transfer first.",
        "operationId": "delete_admin_user_api_admin_users__admin_id__delete",
        "parameters": [
          {
            "in": "path",
            "name": "admin_id",
            "required": true,
            "schema": {
              "format": "uuid",
              "title": "Admin Id",
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Delete Admin User",
        "tags": [
          "admin-users"
        ]
      },
      "patch": {
        "operationId": "update_admin_user_api_admin_users__admin_id__patch",
        "parameters": [
          {
            "in": "path",
            "name": "admin_id",
            "required": true,
            "schema": {
              "format": "uuid",
              "title": "Admin Id",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AdminUserUpdate"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminUserRead"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Update Admin User",
        "tags": [
          "admin-users"
        ]
      }
    },
    "/api/admin/users/{admin_id}/activate": {
      "post": {
        "description": "Approve a pending account, choosing its role in the same action.",
        "operationId": "activate_admin_user_api_admin_users__admin_id__activate_post",
        "parameters": [
          {
            "in": "path",
            "name": "admin_id",
            "required": true,
            "schema": {
              "format": "uuid",
              "title": "Admin Id",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/Body_activate_admin_user_api_admin_users__admin_id__activate_post"
              }
            }
          }
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminUserRead"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Activate Admin User",
        "tags": [
          "admin-users"
        ]
      }
    },
    "/api/admin/users/{admin_id}/reject": {
      "post": {
        "description": "Turn a pending account away.\n\nWithdrawn rather than deleted, deliberately. A deleted row would be recreated as a fresh\npending account by the next sign-in from that address, and the super admin who already\nsaid no would have to say it again every time.",
        "operationId": "reject_admin_user_api_admin_users__admin_id__reject_post",
        "parameters": [
          {
            "in": "path",
            "name": "admin_id",
            "required": true,
            "schema": {
              "format": "uuid",
              "title": "Admin Id",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminUserRead"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Reject Admin User",
        "tags": [
          "admin-users"
        ]
      }
    },
    "/api/admin/users/{admin_id}/temporary-password": {
      "post": {
        "description": "Issue a fresh temporary password. The only recovery path for a password account.\n\nThere is no self-service forgotten-password flow: the only outbound mail this system has\nis guest-facing, with suppression lists and quiet hours, and routing admin reset links\nthrough it would put them behind a guest's unsubscribe state (design D14).\n\nThe value is returned here and nowhere else, ever.",
        "operationId": "issue_temporary_password_api_admin_users__admin_id__temporary_password_post",
        "parameters": [
          {
            "in": "path",
            "name": "admin_id",
            "required": true,
            "schema": {
              "format": "uuid",
              "title": "Admin Id",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TemporaryPasswordRead"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Issue Temporary Password",
        "tags": [
          "admin-users"
        ]
      }
    },
    "/api/admin/users/{admin_id}/withdraw": {
      "post": {
        "description": "End an active account's access, immediately \u2014 not at their next sign-in.",
        "operationId": "withdraw_admin_user_api_admin_users__admin_id__withdraw_post",
        "parameters": [
          {
            "in": "path",
            "name": "admin_id",
            "required": true,
            "schema": {
              "format": "uuid",
              "title": "Admin Id",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AdminUserRead"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Withdraw Admin User",
        "tags": [
          "admin-users"
        ]
      }
    },
    "/api/admin/wedding": {
      "get": {
        "operationId": "read_wedding_api_admin_wedding_get",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WeddingRead"
                }
              }
            },
            "description": "Successful Response"
          }
        },
        "summary": "Read Wedding",
        "tags": [
          "admin"
        ]
      },
      "patch": {
        "operationId": "update_wedding_settings_api_admin_wedding_patch",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WeddingSettingsWrite"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WeddingRead"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Update Wedding Settings",
        "tags": [
          "admin"
        ]
      }
    },
    "/api/admin/wedding/invitation-messages": {
      "put": {
        "description": "Replace the customer-editable greeting sentences.\n\nWedding-wide, so Super Admin only: this is the fallback every event inherits when it has\nno wording of its own. A Host has `PUT /admin/events/{id}` for their own greetings, which\nis the surface they actually want (task 8.7).\n\nValidation happens here rather than in the Pydantic model so the 422 carries the message\nthe host needs \u2014 which field, how long it actually is, and why the limit exists.",
        "operationId": "write_invitation_messages_api_admin_wedding_invitation_messages_put",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InvitationMessagesWrite"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WeddingRead"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Write Invitation Messages",
        "tags": [
          "admin"
        ]
      }
    },
    "/api/auth/dev": {
      "post": {
        "description": "Sign in without Google and without a password, for local development only.\n\nTwo independent conditions must both hold \u2014 the flag and a non-production\nenvironment \u2014 and `assert_production_ready` refuses to boot prod with the flag set. The\nemail must still be on the roster and active, so this weakens authentication, not\nauthorization. It provisions nothing, and it does not lift the temporary-password\nconfinement: `get_unconfined_admin` reads that from the row, not from how you arrived.",
        "operationId": "sign_in_dev_api_auth_dev_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DevSignInRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SessionRead"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Sign In Dev",
        "tags": [
          "auth"
        ]
      }
    },
    "/api/auth/google": {
      "post": {
        "operationId": "sign_in_with_google_api_auth_google_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GoogleSignInRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SessionRead"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Sign In With Google",
        "tags": [
          "auth"
        ]
      }
    },
    "/api/auth/me": {
      "get": {
        "operationId": "read_current_session_api_auth_me_get",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SessionRead"
                }
              }
            },
            "description": "Successful Response"
          }
        },
        "summary": "Read Current Session",
        "tags": [
          "auth"
        ]
      }
    },
    "/api/auth/password": {
      "post": {
        "description": "Username and password against an account a super admin created (design D15, D16).\n\nEvery failure below answers with the same 401 and the same message. An unknown username,\na Google account's address, a wrong password and a locked account are four different\nsituations and exactly one response, because the difference between them is what an\nattacker working through a list is trying to measure.",
        "operationId": "sign_in_with_password_api_auth_password_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PasswordSignInRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SessionRead"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Sign In With Password",
        "tags": [
          "auth"
        ]
      }
    },
    "/api/auth/password/change": {
      "post": {
        "description": "Change your own password. Never anyone else's \u2014 that is the roster's job, and it\nproduces a temporary password rather than a chosen one (spec admin-auth).",
        "operationId": "change_own_password_api_auth_password_change_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PasswordChangeRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SessionRead"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Change Own Password",
        "tags": [
          "auth"
        ]
      }
    },
    "/api/auth/signout": {
      "post": {
        "operationId": "sign_out_api_auth_signout_post",
        "responses": {
          "204": {
            "description": "Successful Response"
          }
        },
        "summary": "Sign Out",
        "tags": [
          "auth"
        ]
      }
    },
    "/api/events/{slug}": {
      "get": {
        "description": "Open/QR route. Public and identical for every visitor, so it may be cached.\n\nCarries no personal data \u2014 a walk-in is unknown until they register.",
        "operationId": "read_open_event_api_events__slug__get",
        "parameters": [
          {
            "in": "path",
            "name": "slug",
            "required": true,
            "schema": {
              "title": "Slug",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OpenEventRead"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Read Open Event",
        "tags": [
          "invitation"
        ]
      }
    },
    "/api/events/{slug}/rsvp": {
      "post": {
        "description": "Walk-in registration from a printed QR or shared link (FR-2.10, FR-2.11).\n\nTurnstile applies to this route only \u2014 the tokenized flow stays frictionless.",
        "operationId": "open_link_rsvp_api_events__slug__rsvp_post",
        "parameters": [
          {
            "in": "path",
            "name": "slug",
            "required": true,
            "schema": {
              "title": "Slug",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OpenRsvpRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OpenRsvpConfirmation"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Open Link Rsvp",
        "tags": [
          "rsvp"
        ]
      }
    },
    "/api/health": {
      "get": {
        "operationId": "health_api_health_get",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "additionalProperties": true,
                  "title": "Response Health Api Health Get",
                  "type": "object"
                }
              }
            },
            "description": "Successful Response"
          }
        },
        "summary": "Health",
        "tags": [
          "health"
        ]
      }
    },
    "/api/ics/{token}": {
      "get": {
        "operationId": "download_ics_api_ics__token__get",
        "parameters": [
          {
            "in": "path",
            "name": "token",
            "required": true,
            "schema": {
              "title": "Token",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {}
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Download Ics",
        "tags": [
          "calendar"
        ]
      }
    },
    "/api/invitations/by-code/{short_code}": {
      "get": {
        "description": "Printed-card fallback: 'Or visit domain.com/rsvp and enter code A7K2M9'\n(spec qr-codes FR-7.5).\n\nSomeone typing a code off a printed card is a guest by definition, so `preview` is\ncarried through only to keep the two routes one behaviour rather than two.",
        "operationId": "read_invitation_by_short_code_api_invitations_by_code__short_code__get",
        "parameters": [
          {
            "in": "path",
            "name": "short_code",
            "required": true,
            "schema": {
              "title": "Short Code",
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "preview",
            "required": false,
            "schema": {
              "default": false,
              "title": "Preview",
              "type": "boolean"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TokenInvitationRead"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Read Invitation By Short Code",
        "tags": [
          "invitation"
        ]
      }
    },
    "/api/invitations/by-token/{token}": {
      "get": {
        "description": "Personalised invitation. Records the open, then returns the page payload.\n\n`preview=1` returns the identical payload without recording the open (design D9). It is\nfor the case where the fetch is a link-preview crawler rather than a guest: an admin\npasting an invitation into WhatsApp would otherwise have the platform's crawler mark\nthat guest's invitation opened before the guest had seen it.\n\nOnly the recording is skipped. Nothing about what is returned changes, so this cannot\nbecome a way to read an invitation that the normal route would refuse.",
        "operationId": "read_invitation_by_token_api_invitations_by_token__token__get",
        "parameters": [
          {
            "in": "path",
            "name": "token",
            "required": true,
            "schema": {
              "title": "Token",
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "preview",
            "required": false,
            "schema": {
              "default": false,
              "title": "Preview",
              "type": "boolean"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TokenInvitationRead"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Read Invitation By Token",
        "tags": [
          "invitation"
        ]
      }
    },
    "/api/rsvp/{token}/accept": {
      "post": {
        "operationId": "accept_invitation_api_rsvp__token__accept_post",
        "parameters": [
          {
            "in": "path",
            "name": "token",
            "required": true,
            "schema": {
              "title": "Token",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RsvpAcceptRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RsvpConfirmation"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Accept Invitation",
        "tags": [
          "rsvp"
        ]
      }
    },
    "/api/rsvp/{token}/cancel": {
      "post": {
        "description": "POST-only, and the body must carry `confirm: true`.\n\nThere is no GET counterpart by design: link scanners must not be able to cancel.",
        "operationId": "cancel_invitation_api_rsvp__token__cancel_post",
        "parameters": [
          {
            "in": "path",
            "name": "token",
            "required": true,
            "schema": {
              "title": "Token",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RsvpCancelRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RsvpConfirmation"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Cancel Invitation",
        "tags": [
          "rsvp"
        ]
      }
    },
    "/api/rsvp/{token}/decline": {
      "post": {
        "operationId": "decline_invitation_api_rsvp__token__decline_post",
        "parameters": [
          {
            "in": "path",
            "name": "token",
            "required": true,
            "schema": {
              "title": "Token",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RsvpDeclineRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RsvpConfirmation"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Decline Invitation",
        "tags": [
          "rsvp"
        ]
      }
    },
    "/api/rsvp/{token}/reaccept": {
      "post": {
        "description": "'Changed your mind?' after a cancellation or decline (FR-3.6).",
        "operationId": "reaccept_invitation_api_rsvp__token__reaccept_post",
        "parameters": [
          {
            "in": "path",
            "name": "token",
            "required": true,
            "schema": {
              "title": "Token",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RsvpAcceptRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RsvpConfirmation"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Reaccept Invitation",
        "tags": [
          "rsvp"
        ]
      }
    },
    "/api/unsubscribe/{token}": {
      "get": {
        "description": "Read-only preview. Unlike the invitation route this does not mark the invitation\nopened \u2014 following an unsubscribe link is not engagement with the invitation.",
        "operationId": "read_unsubscribe_api_unsubscribe__token__get",
        "parameters": [
          {
            "in": "path",
            "name": "token",
            "required": true,
            "schema": {
              "title": "Token",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UnsubscribeRead"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Read Unsubscribe",
        "tags": [
          "invitation"
        ]
      },
      "post": {
        "description": "Suppress further messages for this event only.\n\nThe invitation link keeps working (spec messaging FR-6.11): opting out of reminders is\nnot the same as withdrawing from the wedding, and a guest who later wants to change\ntheir RSVP must still be able to.",
        "operationId": "unsubscribe_api_unsubscribe__token__post",
        "parameters": [
          {
            "in": "path",
            "name": "token",
            "required": true,
            "schema": {
              "title": "Token",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UnsubscribeRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UnsubscribeRead"
                }
              }
            },
            "description": "Successful Response"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            },
            "description": "Validation Error"
          }
        },
        "summary": "Unsubscribe",
        "tags": [
          "invitation"
        ]
      }
    },
    "/api/webhooks/email": {
      "post": {
        "description": "Delivery and bounce receipts (FR-6.11).\n\nThe signature is verified before the body is parsed. An unsigned webhook could mark\nreal sends delivered, or flag a good address as hard-bounced and silence that guest for\nthe rest of the wedding.",
        "operationId": "email_webhook_api_webhooks_email_post",
        "responses": {
          "204": {
            "description": "Successful Response"
          }
        },
        "summary": "Email Webhook",
        "tags": [
          "admin"
        ]
      }
    }
  }
}
