{
  "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://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, the scopes the key carries, and the host's plan: the year's end date (null if they never bought one) and, while it runs, this month's texts used and left.",
        "tags": [
          "me"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "x-scope": "read",
        "x-mcp-tool": "whoami",
        "parameters": [],
        "responses": {
          "200": {
            "description": "The host, the plan, and the key that authenticated.",
            "content": {
              "application/json": {
                "example": {
                  "host": {
                    "id": "seed_host_rachel",
                    "first_name": "Rachel",
                    "tz": "America/New_York"
                  },
                  "plan": {
                    "year_until": "2027-06-01T00:00:00.000Z",
                    "texts_used_this_month": 12,
                    "texts_left_this_month": 138
                  },
                  "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"
                }
              }
            }
          },
          "409": {
            "description": "The state machine refused the request — sending a cancelled event, nudging one that was never sent, asking for a phone number an invitation does not have.",
            "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"
                }
              }
            }
          },
          "500": {
            "description": "A fault on our side, not in the request. Retrying later is reasonable.",
            "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 or while the host's year runs."
          },
          "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"
                }
              }
            }
          },
          "409": {
            "description": "The state machine refused the request — sending a cancelled event, nudging one that was never sent, asking for a phone number an invitation does not have.",
            "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"
                }
              }
            }
          },
          "500": {
            "description": "A fault on our side, not in the request. Retrying later is reasonable.",
            "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"
                }
              }
            }
          },
          "409": {
            "description": "The state machine refused the request — sending a cancelled event, nudging one that was never sent, asking for a phone number an invitation does not have.",
            "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"
                }
              }
            }
          },
          "500": {
            "description": "A fault on our side, not in the request. Retrying later is reasonable.",
            "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."
                  },
                  "all_day": {
                    "type": "boolean",
                    "description": "True for an event that takes the whole day, or several. starts_at is read in tz and moves back to the midnight that opens its day, and ends_at moves forward to the next midnight, so ends_at is the midnight after the last day. With no ends_at it is one day. Guests read it as All day, with no time and no zone. Needs starts_at."
                  },
                  "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",
                      "midnight",
                      null
                    ],
                    "description": "The gradient an aurora invitation wears. null derives it from the title. Ignored by every other look."
                  },
                  "look_styles": {
                    "description": "Each look's option, keyed by look, e.g. {\"marquee\": \"light\", \"reserve\": \"bottle\"}. Folio: black, navy, oxblood. Marquee: dark, light. Reserve: charcoal, bottle, oxblood. Atlas: streets, night. Bento: photo, date, place. Poster: ink, forest, indigo. Lumen: linen, slate, walnut. A value the look does not offer is ignored."
                  },
                  "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\"."
                  },
                  "text_for_me": {
                    "type": "boolean",
                    "description": "Text the invite itself to this event's mobile-only guests, who answer by replying Y or N. Setting true needs attest: true, and is refused unless the host's year is active and Campaign B is live."
                  },
                  "attest": {
                    "const": true,
                    "description": "Required, and must be true, whenever text_for_me is true. It is the host's word, recorded with this request: \"I know these people and have their permission to get a text invite from me via SimpleInvite. Message and data rates may apply; they can reply STOP any time.\" Send it only when the host has agreed to that sentence."
                  }
                },
                "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, guest_cap and text_for_me. An event is free up to 30 invitations; paying its $5 raises guest_cap to 150 and sets paid_at, and the host's year raises it to 150 on every event. text_for_me is true only when the host has switched on Text for me for this event. all_day is true for an event that takes the whole day, or several: starts_at is midnight in tz on its first day, ends_at is the midnight after its last day, and time_known stays 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"
                }
              }
            }
          },
          "409": {
            "description": "The state machine refused the request — sending a cancelled event, nudging one that was never sent, asking for a phone number an invitation does not have.",
            "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"
                }
              }
            }
          },
          "500": {
            "description": "A fault on our side, not in the request. Retrying later is reasonable.",
            "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. Setting text_for_me: true asks us to text the invite itself to this event's mobile-only guests, who answer by replying Y or N — refused with a 409 unless the host's year is active and SMS_CAMPAIGN_B is live; the field is otherwise ignored on a host who is not eligible, and set back to false there.",
        "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"
                }
              }
            }
          },
          "409": {
            "description": "The state machine refused the request — sending a cancelled event, nudging one that was never sent, asking for a phone number an invitation does not have.",
            "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"
                }
              }
            }
          },
          "500": {
            "description": "A fault on our side, not in the request. Retrying later is reasonable.",
            "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."
                  },
                  "all_day": {
                    "type": "boolean",
                    "description": "True for an event that takes the whole day, or several. starts_at is read in tz and moves back to the midnight that opens its day, and ends_at moves forward to the next midnight, so ends_at is the midnight after the last day. With no ends_at it is one day. Guests read it as All day, with no time and no zone. Needs starts_at."
                  },
                  "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",
                      "midnight",
                      null
                    ],
                    "description": "The gradient an aurora invitation wears. null derives it from the title. Ignored by every other look."
                  },
                  "look_styles": {
                    "description": "Each look's option, keyed by look, e.g. {\"marquee\": \"light\", \"reserve\": \"bottle\"}. Folio: black, navy, oxblood. Marquee: dark, light. Reserve: charcoal, bottle, oxblood. Atlas: streets, night. Bento: photo, date, place. Poster: ink, forest, indigo. Lumen: linen, slate, walnut. A value the look does not offer is ignored."
                  },
                  "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\"."
                  },
                  "text_for_me": {
                    "type": "boolean",
                    "description": "Text the invite itself to this event's mobile-only guests, who answer by replying Y or N. Setting true needs attest: true, and is refused unless the host's year is active and Campaign B is live."
                  },
                  "attest": {
                    "const": true,
                    "description": "Required, and must be true, whenever text_for_me is true. It is the host's word, recorded with this request: \"I know these people and have their permission to get a text invite from me via SimpleInvite. Message and data rates may apply; they can reply STOP any time.\" Send it only when the host has agreed to that sentence."
                  }
                },
                "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"
                }
              }
            }
          },
          "409": {
            "description": "The state machine refused the request — sending a cancelled event, nudging one that was never sent, asking for a phone number an invitation does not have.",
            "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"
                }
              }
            }
          },
          "500": {
            "description": "A fault on our side, not in the request. Retrying later is reasonable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "text": {
                    "type": "string",
                    "maxLength": 300,
                    "description": "What to tell the people who were coming, up to 300 characters. 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 or covered by the host's year, and no event may exceed 150. Paying happens in the app, not over this API. A later date of a repeating series is refused with a 409 until it goes out on its own: its invites are already booked for its lead day, and the 409 message says when.",
        "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"
                }
              }
            }
          },
          "409": {
            "description": "The state machine refused the request — sending a cancelled event, nudging one that was never sent, asking for a phone number an invitation does not have.",
            "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"
                }
              }
            }
          },
          "500": {
            "description": "A fault on our side, not in the request. Retrying later is reasonable.",
            "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. Only people an invite has gone to are nudged: a guest added after the event was sent comes back under skipped as not_invited, and /events/{id}/send invites them.",
        "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"
                }
              }
            }
          },
          "409": {
            "description": "The state machine refused the request — sending a cancelled event, nudging one that was never sent, asking for a phone number an invitation does not have.",
            "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"
                }
              }
            }
          },
          "500": {
            "description": "A fault on our side, not in the request. Retrying later is reasonable.",
            "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": 300,
                    "description": "Your own words, up to 300 characters. Email and the invite page carry them whole; a text from our number fits them to one text. 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"
                }
              }
            }
          },
          "409": {
            "description": "The state machine refused the request — sending a cancelled event, nudging one that was never sent, asking for a phone number an invitation does not have.",
            "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"
                }
              }
            }
          },
          "500": {
            "description": "A fault on our side, not in the request. Retrying later is reasonable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "text": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 300,
                    "description": "What you want to tell them, up to 300 characters. Email and the invite page carry it whole; a text from our number fits it to one text."
                  },
                  "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"
                }
              }
            }
          },
          "409": {
            "description": "The state machine refused the request — sending a cancelled event, nudging one that was never sent, asking for a phone number an invitation does not have.",
            "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"
                }
              }
            }
          },
          "500": {
            "description": "A fault on our side, not in the request. Retrying later is reasonable.",
            "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, headcount and texts field: whether the guest opted in to texts from our number, since when, which variant, and whether they opted out of all texts (STOP)."
          },
          "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"
                }
              }
            }
          },
          "409": {
            "description": "The state machine refused the request — sending a cancelled event, nudging one that was never sent, asking for a phone number an invitation does not have.",
            "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"
                }
              }
            }
          },
          "500": {
            "description": "A fault on our side, not in the request. Retrying later is reasonable.",
            "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; on an event that has already gone out, that invites only the people no invite has gone to yet. A draft's guest_cap is enforced at send. On an event that has already gone out it is enforced here, with a 409: 30 invitations free, 150 once the event's $5 is paid or while the host's year runs. Refused with a 409 on a cancelled event.",
        "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"
                }
              }
            }
          },
          "409": {
            "description": "The state machine refused the request — sending a cancelled event, nudging one that was never sent, asking for a phone number an invitation does not have.",
            "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"
                }
              }
            }
          },
          "500": {
            "description": "A fault on our side, not in the request. Retrying later is reasonable.",
            "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, including a texts field: whether the guest opted in to texts from our number, since when, which variant, and whether they opted out of all texts (STOP)."
          },
          "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"
                }
              }
            }
          },
          "409": {
            "description": "The state machine refused the request — sending a cancelled event, nudging one that was never sent, asking for a phone number an invitation does not have.",
            "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"
                }
              }
            }
          },
          "500": {
            "description": "A fault on our side, not in the request. Retrying later is reasonable.",
            "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"
                }
              }
            }
          },
          "409": {
            "description": "The state machine refused the request — sending a cancelled event, nudging one that was never sent, asking for a phone number an invitation does not have.",
            "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"
                }
              }
            }
          },
          "500": {
            "description": "A fault on our side, not in the request. Retrying later is reasonable.",
            "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"
                }
              }
            }
          },
          "409": {
            "description": "The state machine refused the request — sending a cancelled event, nudging one that was never sent, asking for a phone number an invitation does not have.",
            "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"
                }
              }
            }
          },
          "500": {
            "description": "A fault on our side, not in the request. Retrying later is reasonable.",
            "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. Refused with a 409 for an invite that has not gone out yet (a draft, a series date before its lead day, or a guest added after the event was sent: post to /events/{id}/send to invite those), and on a cancelled event.",
        "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"
                }
              }
            }
          },
          "409": {
            "description": "The state machine refused the request — sending a cancelled event, nudging one that was never sent, asking for a phone number an invitation does not have.",
            "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"
                }
              }
            }
          },
          "500": {
            "description": "A fault on our side, not in the request. Retrying later is reasonable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/invitations/{id}/text": {
      "get": {
        "operationId": "getInvitationText",
        "summary": "The host's own text for one guest.",
        "description": "Builds the words and the sms: link for the host to send this guest's invite from their own phone — we never send it. 409 when the guest has no mobile number.",
        "tags": [
          "invitations"
        ],
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "x-scope": "write",
        "x-mcp-tool": "get_host_text",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "An id a previous response returned."
          }
        ],
        "responses": {
          "200": {
            "description": "to, body and href — hand href or body to the host to send themselves.",
            "content": {
              "application/json": {
                "example": {
                  "invitation_id": "inv_1",
                  "to": "+15551234567",
                  "body": "You're invited: Backyard cookout, Sat Sep 12 at 6:00 PM. Let me know here: https://nvite.app/ab12cd34ef",
                  "href": "sms:+15551234567?&body=You%27re%20invited%3A%20Backyard%20cookout%2C%20Sat%20Sep%2012%20at%206%3A00%20PM.%20Let%20me%20know%20here%3A%20https%3A%2F%2Fnvite.app%2Fab12cd34ef"
                }
              }
            }
          },
          "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"
                }
              }
            }
          },
          "409": {
            "description": "The state machine refused the request — sending a cancelled event, nudging one that was never sent, asking for a phone number an invitation does not have.",
            "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"
                }
              }
            }
          },
          "500": {
            "description": "A fault on our side, not in the request. Retrying later is reasonable.",
            "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"
                }
              }
            }
          },
          "409": {
            "description": "The state machine refused the request — sending a cancelled event, nudging one that was never sent, asking for a phone number an invitation does not have.",
            "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"
                }
              }
            }
          },
          "500": {
            "description": "A fault on our side, not in the request. Retrying later is reasonable.",
            "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"
                }
              }
            }
          },
          "409": {
            "description": "The state machine refused the request — sending a cancelled event, nudging one that was never sent, asking for a phone number an invitation does not have.",
            "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"
                }
              }
            }
          },
          "500": {
            "description": "A fault on our side, not in the request. Retrying later is reasonable.",
            "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"
                }
              }
            }
          },
          "409": {
            "description": "The state machine refused the request — sending a cancelled event, nudging one that was never sent, asking for a phone number an invitation does not have.",
            "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"
                }
              }
            }
          },
          "500": {
            "description": "A fault on our side, not in the request. Retrying later is reasonable.",
            "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"
                }
              }
            }
          },
          "409": {
            "description": "The state machine refused the request — sending a cancelled event, nudging one that was never sent, asking for a phone number an invitation does not have.",
            "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"
                }
              }
            }
          },
          "500": {
            "description": "A fault on our side, not in the request. Retrying later is reasonable.",
            "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"
                }
              }
            }
          },
          "409": {
            "description": "The state machine refused the request — sending a cancelled event, nudging one that was never sent, asking for a phone number an invitation does not have.",
            "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"
                }
              }
            }
          },
          "500": {
            "description": "A fault on our side, not in the request. Retrying later is reasonable.",
            "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"
                }
              }
            }
          },
          "409": {
            "description": "The state machine refused the request — sending a cancelled event, nudging one that was never sent, asking for a phone number an invitation does not have.",
            "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"
                }
              }
            }
          },
          "500": {
            "description": "A fault on our side, not in the request. Retrying later is reasonable.",
            "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"
                }
              }
            }
          },
          "409": {
            "description": "The state machine refused the request — sending a cancelled event, nudging one that was never sent, asking for a phone number an invitation does not have.",
            "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"
                }
              }
            }
          },
          "500": {
            "description": "A fault on our side, not in the request. Retrying later is reasonable.",
            "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"
                }
              }
            }
          },
          "409": {
            "description": "The state machine refused the request — sending a cancelled event, nudging one that was never sent, asking for a phone number an invitation does not have.",
            "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"
                }
              }
            }
          },
          "500": {
            "description": "A fault on our side, not in the request. Retrying later is reasonable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 24,
                    "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"
                }
              }
            }
          },
          "409": {
            "description": "The state machine refused the request — sending a cancelled event, nudging one that was never sent, asking for a phone number an invitation does not have.",
            "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"
                }
              }
            }
          },
          "500": {
            "description": "A fault on our side, not in the request. Retrying later is reasonable.",
            "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"
                }
              }
            }
          },
          "409": {
            "description": "The state machine refused the request — sending a cancelled event, nudging one that was never sent, asking for a phone number an invitation does not have.",
            "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"
                }
              }
            }
          },
          "500": {
            "description": "A fault on our side, not in the request. Retrying later is reasonable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 24
                  },
                  "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"
                }
              }
            }
          },
          "409": {
            "description": "The state machine refused the request — sending a cancelled event, nudging one that was never sent, asking for a phone number an invitation does not have.",
            "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"
                }
              }
            }
          },
          "500": {
            "description": "A fault on our side, not in the request. Retrying later is reasonable.",
            "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"
                }
              }
            }
          },
          "409": {
            "description": "The state machine refused the request — sending a cancelled event, nudging one that was never sent, asking for a phone number an invitation does not have.",
            "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"
                }
              }
            }
          },
          "500": {
            "description": "A fault on our side, not in the request. Retrying later is reasonable.",
            "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"
                }
              }
            }
          },
          "409": {
            "description": "The state machine refused the request — sending a cancelled event, nudging one that was never sent, asking for a phone number an invitation does not have.",
            "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"
                }
              }
            }
          },
          "500": {
            "description": "A fault on our side, not in the request. Retrying later is reasonable.",
            "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. A host can register up to 10 webhooks, each URL once; past either, a 409.",
        "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"
                }
              }
            }
          },
          "409": {
            "description": "The state machine refused the request — sending a cancelled event, nudging one that was never sent, asking for a phone number an invitation does not have.",
            "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"
                }
              }
            }
          },
          "500": {
            "description": "A fault on our side, not in the request. Retrying later is reasonable.",
            "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": [
          {
            "name": "id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 1,
              "description": "The webhook's id, from a previous list or create."
            },
            "description": "The webhook's id, from a previous list or create."
          }
        ],
        "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"
                }
              }
            }
          },
          "409": {
            "description": "The state machine refused the request — sending a cancelled event, nudging one that was never sent, asking for a phone number an invitation does not have.",
            "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"
                }
              }
            }
          },
          "500": {
            "description": "A fault on our side, not in the request. Retrying later is reasonable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "webhooks": {
    "rsvp.changed": {
      "post": {
        "summary": "rsvp.changed: an answer was recorded or changed.",
        "description": "Signed with the webhook's secret in the Simpleinvite-Signature header. Answer 2xx as soon as you have the body; we try twice, a second apart.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string",
                    "description": "Unique to this delivery."
                  },
                  "type": {
                    "const": "rsvp.changed"
                  },
                  "created_at": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "data": {
                    "type": "object",
                    "properties": {
                      "event_id": {
                        "type": "string"
                      },
                      "invitation_id": {
                        "type": "string"
                      },
                      "status": {
                        "type": "string",
                        "enum": [
                          "awaiting",
                          "accepted",
                          "declined"
                        ]
                      },
                      "maybe": {
                        "type": "boolean",
                        "description": "True when the answer is not sure yet. status stays awaiting."
                      },
                      "headcount": {
                        "type": "integer",
                        "description": "Adults and children coming on this invitation."
                      },
                      "response_channel": {
                        "type": "string",
                        "enum": [
                          "web",
                          "sms",
                          "email",
                          "host"
                        ],
                        "description": "How the answer came in: the invite page, a text, an email reply, or the host."
                      },
                      "responded_at": {
                        "type": "string",
                        "format": "date-time"
                      }
                    },
                    "additionalProperties": false,
                    "required": [
                      "event_id",
                      "invitation_id",
                      "status",
                      "maybe",
                      "headcount",
                      "response_channel",
                      "responded_at"
                    ],
                    "description": "rsvp.changed: an answer was recorded or changed."
                  }
                },
                "required": [
                  "id",
                  "type",
                  "created_at",
                  "data"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Any 2xx acknowledges the delivery."
          }
        }
      }
    },
    "message.received": {
      "post": {
        "summary": "message.received: a guest's text or email reply was added to an event's thread. data is that message, direction in.",
        "description": "Signed with the webhook's secret in the Simpleinvite-Signature header. Answer 2xx as soon as you have the body; we try twice, a second apart.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string",
                    "description": "Unique to this delivery."
                  },
                  "type": {
                    "const": "message.received"
                  },
                  "created_at": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "data": {
                    "type": "object",
                    "properties": {
                      "id": {
                        "type": "string"
                      },
                      "event_id": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "invitation_id": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "direction": {
                        "type": "string",
                        "enum": [
                          "in",
                          "out"
                        ]
                      },
                      "channel": {
                        "type": "string",
                        "enum": [
                          "sms",
                          "email",
                          "share"
                        ]
                      },
                      "kind": {
                        "type": "string",
                        "enum": [
                          "invite",
                          "nudge",
                          "dayof",
                          "update",
                          "confirm",
                          "clarify",
                          "help",
                          "forward",
                          "menu",
                          "optout",
                          "cancel"
                        ]
                      },
                      "from": {
                        "type": "string"
                      },
                      "to": {
                        "type": "string"
                      },
                      "body": {
                        "type": "string"
                      },
                      "status": {
                        "type": "string",
                        "enum": [
                          "queued",
                          "sent",
                          "delivered",
                          "failed",
                          "received"
                        ]
                      },
                      "status_at": {
                        "type": "string",
                        "format": "date-time"
                      },
                      "error": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "created_at": {
                        "type": "string",
                        "format": "date-time"
                      }
                    },
                    "additionalProperties": false,
                    "required": [
                      "id",
                      "event_id",
                      "invitation_id",
                      "direction",
                      "channel",
                      "kind",
                      "from",
                      "to",
                      "body",
                      "status",
                      "status_at",
                      "error",
                      "created_at"
                    ],
                    "description": "message.received: a guest's text or email reply was added to an event's thread. data is that message, direction in."
                  }
                },
                "required": [
                  "id",
                  "type",
                  "created_at",
                  "data"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Any 2xx acknowledges the delivery."
          }
        }
      }
    },
    "invitation.delivered": {
      "post": {
        "summary": "invitation.delivered: the text or email carrier confirmed an invite reached the guest. data is that message, kind invite, status delivered. Sent once per invite message.",
        "description": "Signed with the webhook's secret in the Simpleinvite-Signature header. Answer 2xx as soon as you have the body; we try twice, a second apart.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string",
                    "description": "Unique to this delivery."
                  },
                  "type": {
                    "const": "invitation.delivered"
                  },
                  "created_at": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "data": {
                    "type": "object",
                    "properties": {
                      "id": {
                        "type": "string"
                      },
                      "event_id": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "invitation_id": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "direction": {
                        "type": "string",
                        "enum": [
                          "in",
                          "out"
                        ]
                      },
                      "channel": {
                        "type": "string",
                        "enum": [
                          "sms",
                          "email",
                          "share"
                        ]
                      },
                      "kind": {
                        "type": "string",
                        "enum": [
                          "invite",
                          "nudge",
                          "dayof",
                          "update",
                          "confirm",
                          "clarify",
                          "help",
                          "forward",
                          "menu",
                          "optout",
                          "cancel"
                        ]
                      },
                      "from": {
                        "type": "string"
                      },
                      "to": {
                        "type": "string"
                      },
                      "body": {
                        "type": "string"
                      },
                      "status": {
                        "type": "string",
                        "enum": [
                          "queued",
                          "sent",
                          "delivered",
                          "failed",
                          "received"
                        ]
                      },
                      "status_at": {
                        "type": "string",
                        "format": "date-time"
                      },
                      "error": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "created_at": {
                        "type": "string",
                        "format": "date-time"
                      }
                    },
                    "additionalProperties": false,
                    "required": [
                      "id",
                      "event_id",
                      "invitation_id",
                      "direction",
                      "channel",
                      "kind",
                      "from",
                      "to",
                      "body",
                      "status",
                      "status_at",
                      "error",
                      "created_at"
                    ],
                    "description": "invitation.delivered: the text or email carrier confirmed an invite reached the guest. data is that message, kind invite, status delivered. Sent once per invite message."
                  }
                },
                "required": [
                  "id",
                  "type",
                  "created_at",
                  "data"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Any 2xx acknowledges the delivery."
          }
        }
      }
    }
  }
}