{
  "openapi": "3.1.0",
  "info": {
    "title": "SimpleInvite",
    "version": "1.0.0",
    "summary": "Make an event, invite people, see who is coming.",
    "description": "Every route is scoped to the host whose key you hold. Every change goes through the same rules the app itself runs — the state machines, the recipient sets, the sending caps — so nothing you can do here is something the host could not have done by hand.",
    "contact": {
      "email": "help@simpleinvite.app"
    }
  },
  "servers": [
    {
      "url": "https://app.simpleinvite.app/api/v1"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "An API key from your settings, sent as `Authorization: Bearer si_live_...`. Keys carry read and write scopes; a read-only key gets a 403 on any change."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "unauthorized",
                  "forbidden",
                  "not_found",
                  "invalid_request",
                  "conflict",
                  "rate_limited",
                  "internal"
                ],
                "description": "A stable machine word. Branch on this, never on the message."
              },
              "message": {
                "type": "string",
                "description": "A plain sentence saying what to do. Wording may change; the code will not."
              },
              "details": {
                "type": "array",
                "description": "On invalid_request, one entry per field that did not pass.",
                "items": {
                  "type": "object",
                  "properties": {
                    "field": {
                      "type": "string"
                    },
                    "message": {
                      "type": "string"
                    }
                  }
                }
              }
            },
            "required": [
              "code",
              "message"
            ]
          }
        },
        "required": [
          "error"
        ]
      }
    }
  },
  "paths": {
    "/me": {
      "get": {
        "operationId": "getMe",
        "summary": "The host this key acts for.",
        "description": "Make this call first. It proves the key works and returns the host id every other route's rows are scoped to, alongside the scopes the key carries.",
        "tags": [
          "me"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "x-scope": "read",
        "x-mcp-tool": "whoami",
        "parameters": [],
        "responses": {
          "200": {
            "description": "The host, and the key that authenticated.",
            "content": {
              "application/json": {
                "example": {
                  "host": {
                    "id": "seed_host_rachel",
                    "first_name": "Rachel",
                    "tz": "America/New_York"
                  },
                  "key": {
                    "id": "key_1",
                    "name": "My agent",
                    "prefix": "a1b2c3d4",
                    "scopes": [
                      "read",
                      "write"
                    ]
                  }
                }
              }
            }
          },
          "401": {
            "description": "No key, or a key we cannot read. Send it as a bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "A real key without the scope this route needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No row of yours has that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "A field in the body or the query did not pass. details names each one.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "This key is over its budget. Retry-After says when to try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/events": {
      "get": {
        "operationId": "listEvents",
        "summary": "The host's events, newest first.",
        "tags": [
          "events"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "x-scope": "read",
        "x-mcp-tool": "list_events",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "description": "How many rows to return (1-200, default 50).",
              "default": 50
            },
            "description": "How many rows to return (1-200, default 50)."
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "description": "The next_cursor from a previous page. Omit for the first page."
            },
            "description": "The next_cursor from a previous page. Omit for the first page."
          },
          {
            "name": "state",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "live",
                "cancelled",
                "ended"
              ],
              "description": "Return only events in this state."
            },
            "description": "Return only events in this state."
          }
        ],
        "responses": {
          "200": {
            "description": "A page of events, with pagination. Each carries paid_at and guest_cap — 30 invitations free, 150 once the event's $5 is paid."
          },
          "401": {
            "description": "No key, or a key we cannot read. Send it as a bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "A real key without the scope this route needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No row of yours has that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "A field in the body or the query did not pass. details names each one.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "This key is over its budget. Retry-After says when to try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createEvent",
        "summary": "Make an event.",
        "description": "A new event is not sent. Build it over as many calls as you need, then post to /events/{id}/send when it is right.",
        "tags": [
          "events"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "x-scope": "write",
        "x-mcp-tool": "create_event",
        "parameters": [],
        "responses": {
          "200": {
            "description": "The event, with its new id. 201."
          },
          "401": {
            "description": "No key, or a key we cannot read. Send it as a bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "A real key without the scope this route needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No row of yours has that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "A field in the body or the query did not pass. details names each one.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "This key is over its budget. Retry-After says when to try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "title": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 60,
                    "description": "What the event is called. Guests read this."
                  },
                  "starts_at": {
                    "type": "string",
                    "description": "When it starts."
                  },
                  "ends_at": {
                    "type": "string",
                    "description": "When it ends. Optional."
                  },
                  "tz": {
                    "type": "string",
                    "minLength": 1,
                    "description": "The IANA zone the event happens in, for example America/New_York."
                  },
                  "time_known": {
                    "type": "boolean",
                    "description": "False when the time is still to come."
                  },
                  "venue_name": {
                    "type": "string",
                    "maxLength": 120,
                    "description": "The name of the place."
                  },
                  "address": {
                    "type": "string",
                    "maxLength": 240,
                    "description": "The street address, for the map."
                  },
                  "place_notes": {
                    "type": "string",
                    "maxLength": 500,
                    "description": "Parking, the gate code, which door — whatever saves a text."
                  },
                  "notes": {
                    "type": "string",
                    "maxLength": 2000,
                    "description": "Anything else guests should read."
                  },
                  "look": {
                    "type": "string",
                    "enum": [
                      "folio",
                      "marquee",
                      "reserve",
                      "atlas",
                      "bento",
                      "aurora",
                      "poster",
                      "lumen"
                    ],
                    "description": "The invitation's visual style. folio is the default."
                  },
                  "aura": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "sunset",
                      "lagoon",
                      "orchid",
                      "ember"
                    ],
                    "description": "The gradient an aurora invitation wears. null derives it from the title. Ignored by every other look."
                  },
                  "ask_kids": {
                    "type": "boolean",
                    "description": "Ask guests how many children are coming."
                  },
                  "extra_guests_default": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 20,
                    "description": "How many extra people each party may bring."
                  },
                  "cap_mode": {
                    "type": "string",
                    "enum": [
                      "soft",
                      "hard"
                    ],
                    "description": "soft records a party larger than allowed and flags it; hard trims it."
                  },
                  "guest_list_visibility": {
                    "type": "string",
                    "enum": [
                      "after_reply",
                      "always",
                      "never"
                    ],
                    "description": "When a guest may see who else is coming."
                  },
                  "reminders_on": {
                    "type": "boolean",
                    "description": "Send the automatic nudge and the day-of note."
                  },
                  "link_mode": {
                    "type": "string",
                    "enum": [
                      "private",
                      "open"
                    ],
                    "description": "private is one link per party; open is one link anyone may use."
                  },
                  "series_every": {
                    "type": "string",
                    "maxLength": 60,
                    "description": "The host's own words for a repeat, for example \"every Thursday\"."
                  }
                },
                "additionalProperties": false,
                "required": [
                  "title",
                  "tz"
                ]
              }
            }
          }
        }
      }
    },
    "/events/{id}": {
      "get": {
        "operationId": "getEvent",
        "summary": "One event.",
        "tags": [
          "events"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "x-scope": "read",
        "x-mcp-tool": "get_event",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "An id a previous response returned."
          }
        ],
        "responses": {
          "200": {
            "description": "The event, including paid_at and guest_cap. An event is free up to 30 invitations; paying its $5 raises guest_cap to 150 and sets paid_at."
          },
          "401": {
            "description": "No key, or a key we cannot read. Send it as a bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "A real key without the scope this route needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No row of yours has that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "A field in the body or the query did not pass. details names each one.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "This key is over its budget. Retry-After says when to try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "updateEvent",
        "summary": "Change an event. Nothing goes out.",
        "description": "Changing the event and telling the guests are two calls on purpose: fixing a typo in the address should not text forty-two people, and moving the date should. Post to /events/{id}/update to tell them.",
        "tags": [
          "events"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "x-scope": "write",
        "x-mcp-tool": "update_event",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "An id a previous response returned."
          }
        ],
        "responses": {
          "200": {
            "description": "The event as it now stands."
          },
          "401": {
            "description": "No key, or a key we cannot read. Send it as a bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "A real key without the scope this route needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No row of yours has that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "A field in the body or the query did not pass. details names each one.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "This key is over its budget. Retry-After says when to try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "title": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 60,
                    "description": "What the event is called. Guests read this."
                  },
                  "starts_at": {
                    "type": "string",
                    "description": "When it starts."
                  },
                  "ends_at": {
                    "type": "string",
                    "description": "When it ends. Optional."
                  },
                  "tz": {
                    "type": "string",
                    "minLength": 1,
                    "description": "The IANA zone the event happens in, for example America/New_York."
                  },
                  "time_known": {
                    "type": "boolean",
                    "description": "False when the time is still to come."
                  },
                  "venue_name": {
                    "type": "string",
                    "maxLength": 120,
                    "description": "The name of the place."
                  },
                  "address": {
                    "type": "string",
                    "maxLength": 240,
                    "description": "The street address, for the map."
                  },
                  "place_notes": {
                    "type": "string",
                    "maxLength": 500,
                    "description": "Parking, the gate code, which door — whatever saves a text."
                  },
                  "notes": {
                    "type": "string",
                    "maxLength": 2000,
                    "description": "Anything else guests should read."
                  },
                  "look": {
                    "type": "string",
                    "enum": [
                      "folio",
                      "marquee",
                      "reserve",
                      "atlas",
                      "bento",
                      "aurora",
                      "poster",
                      "lumen"
                    ],
                    "description": "The invitation's visual style. folio is the default."
                  },
                  "aura": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "sunset",
                      "lagoon",
                      "orchid",
                      "ember"
                    ],
                    "description": "The gradient an aurora invitation wears. null derives it from the title. Ignored by every other look."
                  },
                  "ask_kids": {
                    "type": "boolean",
                    "description": "Ask guests how many children are coming."
                  },
                  "extra_guests_default": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 20,
                    "description": "How many extra people each party may bring."
                  },
                  "cap_mode": {
                    "type": "string",
                    "enum": [
                      "soft",
                      "hard"
                    ],
                    "description": "soft records a party larger than allowed and flags it; hard trims it."
                  },
                  "guest_list_visibility": {
                    "type": "string",
                    "enum": [
                      "after_reply",
                      "always",
                      "never"
                    ],
                    "description": "When a guest may see who else is coming."
                  },
                  "reminders_on": {
                    "type": "boolean",
                    "description": "Send the automatic nudge and the day-of note."
                  },
                  "link_mode": {
                    "type": "string",
                    "enum": [
                      "private",
                      "open"
                    ],
                    "description": "private is one link per party; open is one link anyone may use."
                  },
                  "series_every": {
                    "type": "string",
                    "maxLength": 60,
                    "description": "The host's own words for a repeat, for example \"every Thursday\"."
                  }
                },
                "additionalProperties": false
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "cancelEvent",
        "summary": "Call the event off.",
        "description": "This cancels rather than deletes. Guests hold live links and answers are on record; removing the row would break every link and discard what people said. Pending sends are stopped first, then the people who acknowledged the invite are told. Someone who never answered is not told.",
        "tags": [
          "events"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "x-scope": "write",
        "x-mcp-tool": "cancel_event",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "An id a previous response returned."
          }
        ],
        "responses": {
          "200": {
            "description": "How many were told, and how many pending sends were stopped."
          },
          "401": {
            "description": "No key, or a key we cannot read. Send it as a bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "A real key without the scope this route needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No row of yours has that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "A field in the body or the query did not pass. details names each one.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "This key is over its budget. Retry-After says when to try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "text": {
                    "type": "string",
                    "maxLength": 480,
                    "description": "What to tell the people who were coming. Omit for the default line."
                  }
                },
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/events/{id}/send": {
      "post": {
        "operationId": "sendEvent",
        "summary": "Send the invitations.",
        "description": "Idempotent. A retry after a timeout returns the first call's breakdown with fresh: false rather than inviting everyone twice. Anyone with an email is emailed by us; everyone else becomes a row under to_share with the exact message and sms: link for the host to send from their own phone. The event's guest_cap is enforced here: an event holding more than 30 invitations must have been paid for, and no event may exceed 150. Paying happens in the app, not over this API.",
        "tags": [
          "events"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "x-scope": "write",
        "x-mcp-tool": "send_event",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "An id a previous response returned."
          }
        ],
        "responses": {
          "200": {
            "description": "Counts, the host's own share list, and anyone unreachable."
          },
          "401": {
            "description": "No key, or a key we cannot read. Send it as a bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "A real key without the scope this route needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No row of yours has that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "A field in the body or the query did not pass. details names each one.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "This key is over its budget. Retry-After says when to try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {},
                "additionalProperties": false,
                "description": "No fields. Sending twice is a no-op: the second call reports the first call's result."
              }
            }
          }
        }
      }
    },
    "/events/{id}/nudge": {
      "post": {
        "operationId": "nudgeEvent",
        "summary": "Chase the people who have not answered.",
        "description": "The caps are not negotiable through this route: one automatic nudge ever, forty-eight hours between host nudges of the same guest, at most two host nudges per guest per event. Anyone capped comes back under skipped with the reason.",
        "tags": [
          "events"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "x-scope": "write",
        "x-mcp-tool": "nudge",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "An id a previous response returned."
          }
        ],
        "responses": {
          "200": {
            "description": "How many were nudged, and who was skipped and why."
          },
          "401": {
            "description": "No key, or a key we cannot read. Send it as a bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "A real key without the scope this route needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No row of yours has that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "A field in the body or the query did not pass. details names each one.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "This key is over its budget. Retry-After says when to try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "invitation_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 64,
                      "description": "An id from a previous response."
                    },
                    "maxItems": 500,
                    "description": "Which parties to nudge. Omit to nudge everyone who has not answered."
                  },
                  "text": {
                    "type": "string",
                    "maxLength": 480,
                    "description": "Your own words. Sent as written. Omit for the default line."
                  }
                },
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/events/{id}/update": {
      "post": {
        "operationId": "sendEventUpdate",
        "summary": "Tell the guests something.",
        "description": "Set reask when the date or the place moved: everyone who accepted is asked to confirm again, their answer is kept, and people who declined are told too, because they may make the new date.",
        "tags": [
          "events"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "x-scope": "write",
        "x-mcp-tool": "send_update",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "An id a previous response returned."
          }
        ],
        "responses": {
          "200": {
            "description": "How many were told and how many were asked again."
          },
          "401": {
            "description": "No key, or a key we cannot read. Send it as a bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "A real key without the scope this route needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No row of yours has that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "A field in the body or the query did not pass. details names each one.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "This key is over its budget. Retry-After says when to try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "text": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 480,
                    "description": "What you want to tell them. Sent as written."
                  },
                  "reask": {
                    "type": "boolean",
                    "description": "True when the date or the place moved: everyone who accepted is asked again.",
                    "default": false
                  }
                },
                "additionalProperties": false,
                "required": [
                  "text"
                ]
              }
            }
          }
        }
      }
    },
    "/events/{id}/summary": {
      "get": {
        "operationId": "getEventSummary",
        "summary": "The counts for one event.",
        "description": "These are the same numbers the host sees on their own screen, from the same function. maybe is reported alongside awaiting rather than inside it, because maybe is a flag on not-having-answered, never a fourth state.",
        "tags": [
          "events"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "x-scope": "read",
        "x-mcp-tool": "get_event_summary",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "An id a previous response returned."
          }
        ],
        "responses": {
          "200": {
            "description": "invited, yes, no, awaiting, maybe, people.",
            "content": {
              "application/json": {
                "example": {
                  "invited": 12,
                  "yes": 7,
                  "no": 2,
                  "awaiting": 3,
                  "maybe": 1,
                  "people": 11
                }
              }
            }
          },
          "401": {
            "description": "No key, or a key we cannot read. Send it as a bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "A real key without the scope this route needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No row of yours has that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "A field in the body or the query did not pass. details names each one.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "This key is over its budget. Retry-After says when to try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/events/{id}/invitations": {
      "get": {
        "operationId": "listInvitations",
        "summary": "The parties on one event.",
        "tags": [
          "events"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "x-scope": "read",
        "x-mcp-tool": "list_invitations",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "An id a previous response returned."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "description": "How many rows to return (1-200, default 50).",
              "default": 50
            },
            "description": "How many rows to return (1-200, default 50)."
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "description": "The next_cursor from a previous page. Omit for the first page."
            },
            "description": "The next_cursor from a previous page. Omit for the first page."
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "awaiting",
                "accepted",
                "declined"
              ],
              "description": "Return only parties in this state."
            },
            "description": "Return only parties in this state."
          }
        ],
        "responses": {
          "200": {
            "description": "A page of invitations, each with its derived label and headcount."
          },
          "401": {
            "description": "No key, or a key we cannot read. Send it as a bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "A real key without the scope this route needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No row of yours has that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "A field in the body or the query did not pass. details names each one.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "This key is over its budget. Retry-After says when to try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createInvitation",
        "summary": "Put one more party on the event.",
        "description": "Idempotent on the person: posting the same guest twice returns the row it already made. Creating does not send — post to /events/{id}/send when the list is right.",
        "tags": [
          "events"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "x-scope": "write",
        "x-mcp-tool": "add_invitation",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "An id a previous response returned."
          }
        ],
        "responses": {
          "200": {
            "description": "The invitation. 201."
          },
          "401": {
            "description": "No key, or a key we cannot read. Send it as a bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "A real key without the scope this route needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No row of yours has that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "A field in the body or the query did not pass. details names each one.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "This key is over its budget. Retry-After says when to try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "guest_id": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 64,
                    "description": "An existing person from your address book."
                  },
                  "first_name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 60,
                    "description": "Their first name, for a new person."
                  },
                  "last_name": {
                    "type": "string",
                    "maxLength": 60
                  },
                  "phone": {
                    "type": "string",
                    "maxLength": 30,
                    "description": "A mobile number, any common format."
                  },
                  "email": {
                    "type": "string",
                    "maxLength": 200
                  },
                  "members": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "name": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 120,
                          "description": "The person's name as it should read."
                        },
                        "guest_id": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 64,
                          "description": "Their address-book id, when they have one."
                        }
                      },
                      "additionalProperties": false,
                      "required": [
                        "name"
                      ]
                    },
                    "maxItems": 20,
                    "description": "Everyone else in their party — a partner, children, a housemate."
                  },
                  "label": {
                    "type": "string",
                    "maxLength": 80,
                    "description": "Your name for the party, for example \"The Kims\"."
                  },
                  "extra_allowed": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 20,
                    "description": "How many extra people this party may bring."
                  }
                },
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/invitations/{id}": {
      "get": {
        "operationId": "getInvitation",
        "summary": "One party.",
        "tags": [
          "invitations"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "x-scope": "read",
        "x-mcp-tool": "get_invitation",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "An id a previous response returned."
          }
        ],
        "responses": {
          "200": {
            "description": "The invitation."
          },
          "401": {
            "description": "No key, or a key we cannot read. Send it as a bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "A real key without the scope this route needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No row of yours has that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "A field in the body or the query did not pass. details names each one.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "This key is over its budget. Retry-After says when to try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "markAnswer",
        "summary": "Record an answer on someone's behalf.",
        "description": "For when a guest told the host in person. It runs the same state machine as a guest's tap, with the response channel recorded as host, so the soft cap, the over-allowance flag and the headcount commit all behave identically. Send answer: reset to put them back to not having answered.",
        "tags": [
          "invitations"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "x-scope": "write",
        "x-mcp-tool": "mark_answer",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "An id a previous response returned."
          }
        ],
        "responses": {
          "200": {
            "description": "The invitation as it now stands, with whether anything changed."
          },
          "401": {
            "description": "No key, or a key we cannot read. Send it as a bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "A real key without the scope this route needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No row of yours has that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "A field in the body or the query did not pass. details names each one.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "This key is over its budget. Retry-After says when to try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "answer": {
                    "type": "string",
                    "enum": [
                      "yes",
                      "no",
                      "maybe",
                      "reset"
                    ],
                    "description": "Record an answer on their behalf. reset puts them back to not having answered."
                  },
                  "adults": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 50,
                    "description": "How many adults are coming."
                  },
                  "kids": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 50,
                    "description": "How many children. Only counted when the event asks."
                  },
                  "label": {
                    "type": "string",
                    "maxLength": 80
                  },
                  "extra_allowed": {
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 20
                  }
                },
                "additionalProperties": false
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "removeInvitation",
        "summary": "Take a party off an event.",
        "tags": [
          "invitations"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "x-scope": "write",
        "x-mcp-tool": "remove_invitation",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "An id a previous response returned."
          }
        ],
        "responses": {
          "200": {
            "description": "The id, and removed: true."
          },
          "401": {
            "description": "No key, or a key we cannot read. Send it as a bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "A real key without the scope this route needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No row of yours has that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "A field in the body or the query did not pass. details names each one.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "This key is over its budget. Retry-After says when to try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/invitations/{id}/resend": {
      "post": {
        "operationId": "resendInvitation",
        "summary": "Send one invitation again.",
        "description": "Not a nudge. Use this when the invite never landed — a bounced address, a number since corrected. The channel is chosen afresh, so a guest whose email bounced and who now has a mobile number gets a text this time.",
        "tags": [
          "invitations"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "x-scope": "write",
        "x-mcp-tool": "resend_invitation",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "An id a previous response returned."
          }
        ],
        "responses": {
          "200": {
            "description": "Whether it went, and on which channel."
          },
          "401": {
            "description": "No key, or a key we cannot read. Send it as a bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "A real key without the scope this route needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No row of yours has that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "A field in the body or the query did not pass. details names each one.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "This key is over its budget. Retry-After says when to try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/guests": {
      "get": {
        "operationId": "listGuests",
        "summary": "The host's address book.",
        "description": "People removed from the book are hidden unless include_removed asks for them — removal is a column, not a delete, because everything they already answered stays.",
        "tags": [
          "guests"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "x-scope": "read",
        "x-mcp-tool": "list_guests",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "description": "How many rows to return (1-200, default 50).",
              "default": 50
            },
            "description": "How many rows to return (1-200, default 50)."
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "description": "The next_cursor from a previous page. Omit for the first page."
            },
            "description": "The next_cursor from a previous page. Omit for the first page."
          },
          {
            "name": "search",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 100,
              "description": "Match a name, a number or an address."
            },
            "description": "Match a name, a number or an address."
          },
          {
            "name": "group_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 64,
              "description": "Only people in this group."
            },
            "description": "Only people in this group."
          },
          {
            "name": "include_removed",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "description": "Include people you took out of your address book."
            },
            "description": "Include people you took out of your address book."
          }
        ],
        "responses": {
          "200": {
            "description": "A page of people."
          },
          "401": {
            "description": "No key, or a key we cannot read. Send it as a bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "A real key without the scope this route needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No row of yours has that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "A field in the body or the query did not pass. details names each one.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "This key is over its budget. Retry-After says when to try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createGuest",
        "summary": "Add a person to the address book.",
        "description": "Find-or-create on the number or the address. Posting the same person twice returns the row that already exists rather than splitting their history across two.",
        "tags": [
          "guests"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "x-scope": "write",
        "x-mcp-tool": "add_guest",
        "parameters": [],
        "responses": {
          "200": {
            "description": "The person. 201."
          },
          "401": {
            "description": "No key, or a key we cannot read. Send it as a bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "A real key without the scope this route needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No row of yours has that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "A field in the body or the query did not pass. details names each one.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "This key is over its budget. Retry-After says when to try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "first_name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 60,
                    "description": "Their first name."
                  },
                  "last_name": {
                    "type": "string",
                    "maxLength": 60
                  },
                  "phone": {
                    "type": "string",
                    "maxLength": 30,
                    "description": "A mobile number, any common format."
                  },
                  "email": {
                    "type": "string",
                    "maxLength": 200
                  },
                  "notes": {
                    "type": "string",
                    "maxLength": 500,
                    "description": "Anything you want to remember about them."
                  },
                  "members": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "name": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 120,
                          "description": "The person's name as it should read."
                        },
                        "guest_id": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 64,
                          "description": "Their address-book id, when they have one."
                        }
                      },
                      "additionalProperties": false,
                      "required": [
                        "name"
                      ]
                    },
                    "maxItems": 20,
                    "description": "The household they come with, so an invitation to them covers everyone."
                  },
                  "party_label": {
                    "type": "string",
                    "maxLength": 80,
                    "description": "Your name for the household."
                  }
                },
                "additionalProperties": false,
                "required": [
                  "first_name"
                ]
              }
            }
          }
        }
      }
    },
    "/guests/{id}": {
      "get": {
        "operationId": "getGuest",
        "summary": "One person.",
        "tags": [
          "guests"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "x-scope": "read",
        "x-mcp-tool": "get_guest",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "An id a previous response returned."
          }
        ],
        "responses": {
          "200": {
            "description": "The person."
          },
          "401": {
            "description": "No key, or a key we cannot read. Send it as a bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "A real key without the scope this route needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No row of yours has that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "A field in the body or the query did not pass. details names each one.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "This key is over its budget. Retry-After says when to try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "updateGuest",
        "summary": "Correct a number, add a household, write a note.",
        "tags": [
          "guests"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "x-scope": "write",
        "x-mcp-tool": "update_guest",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "An id a previous response returned."
          }
        ],
        "responses": {
          "200": {
            "description": "The person as they now stand."
          },
          "401": {
            "description": "No key, or a key we cannot read. Send it as a bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "A real key without the scope this route needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No row of yours has that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "A field in the body or the query did not pass. details names each one.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "This key is over its budget. Retry-After says when to try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "first_name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 60,
                    "description": "Their first name."
                  },
                  "last_name": {
                    "type": "string",
                    "maxLength": 60
                  },
                  "phone": {
                    "type": "string",
                    "maxLength": 30,
                    "description": "A mobile number, any common format."
                  },
                  "email": {
                    "type": "string",
                    "maxLength": 200
                  },
                  "notes": {
                    "type": "string",
                    "maxLength": 500,
                    "description": "Anything you want to remember about them."
                  },
                  "members": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "name": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 120,
                          "description": "The person's name as it should read."
                        },
                        "guest_id": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 64,
                          "description": "Their address-book id, when they have one."
                        }
                      },
                      "additionalProperties": false,
                      "required": [
                        "name"
                      ]
                    },
                    "maxItems": 20,
                    "description": "The household they come with, so an invitation to them covers everyone."
                  },
                  "party_label": {
                    "type": "string",
                    "maxLength": 80,
                    "description": "Your name for the household."
                  }
                },
                "additionalProperties": false
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "removeGuest",
        "summary": "Take a person out of the address book.",
        "description": "Soft. They stop showing up when you add people to an event; every invite already sent, and what they answered, stays.",
        "tags": [
          "guests"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "x-scope": "write",
        "x-mcp-tool": "remove_guest",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "An id a previous response returned."
          }
        ],
        "responses": {
          "200": {
            "description": "The id, and removed: true."
          },
          "401": {
            "description": "No key, or a key we cannot read. Send it as a bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "A real key without the scope this route needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No row of yours has that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "A field in the body or the query did not pass. details names each one.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "This key is over its budget. Retry-After says when to try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/guests/import": {
      "post": {
        "operationId": "importGuests",
        "summary": "Bring a list of people in.",
        "description": "Send a pasted blob under text, or rows under rows. Both go through the same parser the app's own paste box uses. A row that could not be read comes back under skipped with the line exactly as sent — never guessed at. Send dry_run: true first when you are importing on someone's behalf.",
        "tags": [
          "guests"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "x-scope": "write",
        "x-mcp-tool": "import_guests",
        "parameters": [],
        "responses": {
          "200": {
            "description": "How many were read, created, matched and skipped."
          },
          "401": {
            "description": "No key, or a key we cannot read. Send it as a bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "A real key without the scope this route needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No row of yours has that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "A field in the body or the query did not pass. details names each one.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "This key is over its budget. Retry-After says when to try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "text": {
                    "type": "string",
                    "maxLength": 100000,
                    "description": "A pasted list. One person per line, in almost any shape."
                  },
                  "rows": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "first_name": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 60
                        },
                        "last_name": {
                          "type": "string",
                          "maxLength": 60
                        },
                        "phone": {
                          "type": "string",
                          "maxLength": 30
                        },
                        "email": {
                          "type": "string",
                          "maxLength": 200
                        }
                      },
                      "additionalProperties": false,
                      "required": [
                        "first_name"
                      ]
                    },
                    "maxItems": 1000,
                    "description": "Rows you already have in fields."
                  },
                  "default_country": {
                    "type": "string",
                    "minLength": 2,
                    "maxLength": 2,
                    "description": "Two-letter country code for numbers with no + prefix. Defaults to US."
                  },
                  "dry_run": {
                    "type": "boolean",
                    "description": "True to see what would be read without writing anything.",
                    "default": false
                  }
                },
                "additionalProperties": false
              }
            }
          }
        }
      }
    },
    "/groups": {
      "get": {
        "operationId": "listGroups",
        "summary": "The host's saved lists.",
        "tags": [
          "groups"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "x-scope": "read",
        "x-mcp-tool": "list_groups",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "description": "How many rows to return (1-200, default 50).",
              "default": 50
            },
            "description": "How many rows to return (1-200, default 50)."
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "description": "The next_cursor from a previous page. Omit for the first page."
            },
            "description": "The next_cursor from a previous page. Omit for the first page."
          }
        ],
        "responses": {
          "200": {
            "description": "A page of groups with their membership."
          },
          "401": {
            "description": "No key, or a key we cannot read. Send it as a bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "A real key without the scope this route needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No row of yours has that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "A field in the body or the query did not pass. details names each one.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "This key is over its budget. Retry-After says when to try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createGroup",
        "summary": "Make a saved list.",
        "tags": [
          "groups"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "x-scope": "write",
        "x-mcp-tool": "create_group",
        "parameters": [],
        "responses": {
          "200": {
            "description": "The group. 201."
          },
          "401": {
            "description": "No key, or a key we cannot read. Send it as a bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "A real key without the scope this route needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No row of yours has that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "A field in the body or the query did not pass. details names each one.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "This key is over its budget. Retry-After says when to try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 60,
                    "description": "What the group is called, for example \"Game night\"."
                  },
                  "guest_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 64,
                      "description": "An id from a previous response."
                    },
                    "maxItems": 1000,
                    "description": "Who is in it."
                  }
                },
                "additionalProperties": false,
                "required": [
                  "name"
                ]
              }
            }
          }
        }
      }
    },
    "/groups/{id}": {
      "get": {
        "operationId": "getGroup",
        "summary": "One saved list, with who is in it.",
        "tags": [
          "groups"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "x-scope": "read",
        "x-mcp-tool": "get_group",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "An id a previous response returned."
          }
        ],
        "responses": {
          "200": {
            "description": "The group and its guest_ids."
          },
          "401": {
            "description": "No key, or a key we cannot read. Send it as a bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "A real key without the scope this route needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No row of yours has that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "A field in the body or the query did not pass. details names each one.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "This key is over its budget. Retry-After says when to try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "updateGroup",
        "summary": "Rename a list, or replace who is in it.",
        "description": "guest_ids is the whole membership, not an addition.",
        "tags": [
          "groups"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "x-scope": "write",
        "x-mcp-tool": "update_group",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "An id a previous response returned."
          }
        ],
        "responses": {
          "200": {
            "description": "The group as it now stands."
          },
          "401": {
            "description": "No key, or a key we cannot read. Send it as a bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "A real key without the scope this route needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No row of yours has that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "A field in the body or the query did not pass. details names each one.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "This key is over its budget. Retry-After says when to try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 60
                  },
                  "guest_ids": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 64,
                      "description": "An id from a previous response."
                    },
                    "maxItems": 1000,
                    "description": "The whole membership, replacing what was there."
                  }
                },
                "additionalProperties": false
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "removeGroup",
        "summary": "Drop a saved list.",
        "description": "Hard, and safely so: a group is a convenience, not a history. Everyone in it stays in the address book and every invitation ever sent from it stays on its event.",
        "tags": [
          "groups"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "x-scope": "write",
        "x-mcp-tool": "remove_group",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "An id a previous response returned."
          }
        ],
        "responses": {
          "200": {
            "description": "The id, and removed: true."
          },
          "401": {
            "description": "No key, or a key we cannot read. Send it as a bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "A real key without the scope this route needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No row of yours has that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "A field in the body or the query did not pass. details names each one.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "This key is over its budget. Retry-After says when to try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/messages": {
      "get": {
        "operationId": "listMessages",
        "summary": "The host's thread for one event.",
        "description": "Read-only. Sending is send, nudge, update or cancel — each of which applies the recipient rules, the quiet hours, the opt-out check and the caps that a raw message would step around.",
        "tags": [
          "messages"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "x-scope": "read",
        "x-mcp-tool": "list_messages",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "description": "How many rows to return (1-200, default 50).",
              "default": 50
            },
            "description": "How many rows to return (1-200, default 50)."
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "description": "The next_cursor from a previous page. Omit for the first page."
            },
            "description": "The next_cursor from a previous page. Omit for the first page."
          },
          {
            "name": "event_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 64,
              "description": "The event whose thread you want."
            },
            "description": "The event whose thread you want."
          },
          {
            "name": "direction",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "in",
                "out"
              ],
              "description": "Only what came in, or only what went out."
            },
            "description": "Only what came in, or only what went out."
          }
        ],
        "responses": {
          "200": {
            "description": "A page of messages, in and out."
          },
          "401": {
            "description": "No key, or a key we cannot read. Send it as a bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "A real key without the scope this route needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No row of yours has that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "A field in the body or the query did not pass. details names each one.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "This key is over its budget. Retry-After says when to try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/webhooks": {
      "get": {
        "operationId": "listWebhooks",
        "summary": "The URLs this host has registered.",
        "description": "Secrets are never returned by a read.",
        "tags": [
          "webhooks"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "x-scope": "read",
        "x-mcp-tool": "list_webhooks",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Every webhook, without its secret."
          },
          "401": {
            "description": "No key, or a key we cannot read. Send it as a bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "A real key without the scope this route needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No row of yours has that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "A field in the body or the query did not pass. details names each one.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "This key is over its budget. Retry-After says when to try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createWebhook",
        "summary": "Register a URL for event notifications.",
        "description": "The signing secret comes back once, from this call. Store it now — no read returns it again.",
        "tags": [
          "webhooks"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "x-scope": "write",
        "x-mcp-tool": "register_webhook",
        "parameters": [],
        "responses": {
          "200": {
            "description": "The webhook, with its secret. 201."
          },
          "401": {
            "description": "No key, or a key we cannot read. Send it as a bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "A real key without the scope this route needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No row of yours has that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "A field in the body or the query did not pass. details names each one.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "This key is over its budget. Retry-After says when to try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri",
                    "maxLength": 500,
                    "description": "Where to POST the delivery."
                  },
                  "events": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "enum": [
                        "rsvp.changed",
                        "message.received",
                        "invitation.delivered"
                      ],
                      "description": "Something that happens that we can tell you about."
                    },
                    "minItems": 1,
                    "description": "Which events to send. At least one."
                  }
                },
                "additionalProperties": false,
                "required": [
                  "url",
                  "events"
                ]
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "removeWebhook",
        "summary": "Remove a registered URL.",
        "description": "Name the webhook with ?id=.",
        "tags": [
          "webhooks"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "x-scope": "write",
        "x-mcp-tool": "remove_webhook",
        "parameters": [],
        "responses": {
          "200": {
            "description": "The id, and removed: true."
          },
          "401": {
            "description": "No key, or a key we cannot read. Send it as a bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "A real key without the scope this route needs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No row of yours has that id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "A field in the body or the query did not pass. details names each one.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "This key is over its budget. Retry-After says when to try again.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  }
}