{
  "openapi": "3.1.0",
  "info": {
    "title": "headless.events",
    "version": "0.2.0",
    "description": "Agent-first API for headless.events. Every action - create, read, update, delete, RSVP, recover - is a GET URL with query parameters. GET is the lowest-common-denominator across every LLM chat surface (ChatGPT, Claude, Gemini, Grok) on every platform (web, iOS, Android). POST/PATCH/DELETE forms are kept for traditional REST clients only."
  },
  "servers": [
    {
      "url": "https://headless.events"
    }
  ],
  "components": {
    "securitySchemes": {
      "bearer": {
        "type": "http",
        "scheme": "bearer"
      }
    }
  },
  "paths": {
    "/api/events/create": {
      "get": {
        "summary": "Create an event. Pass title, starts_at, etc. as query parameters. Returns JSON: {slug, edit_token, rsvp_url, admin_url, magic_link_emailed}.",
        "operationId": "get_api_events_create",
        "parameters": [
          {
            "name": "title",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "string (required)"
          },
          {
            "name": "starts_at",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "ISO 8601 string (required)"
          },
          {
            "name": "slug",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "optional; derived from title if omitted"
          },
          {
            "name": "description",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "optional"
          },
          {
            "name": "ends_at",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "optional ISO 8601"
          },
          {
            "name": "location",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "optional"
          },
          {
            "name": "host_name",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "optional"
          },
          {
            "name": "host_email",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "optional but recommended; receives a magic-link backup login"
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          },
          "400": {
            "description": "Bad request"
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/api/events": {
      "post": {
        "summary": "POST equivalent of /api/events/create for traditional REST clients. Body is JSON. Agents should prefer the GET form.",
        "operationId": "post_api_events",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "title": {
                    "type": "string",
                    "description": "string (required)"
                  },
                  "starts_at": {
                    "type": "string",
                    "description": "ISO 8601 string (required)"
                  },
                  "slug": {
                    "type": "string",
                    "description": "optional; derived from title if omitted"
                  },
                  "description": {
                    "type": "string",
                    "description": "optional"
                  },
                  "ends_at": {
                    "type": "string",
                    "description": "optional ISO 8601"
                  },
                  "location": {
                    "type": "string",
                    "description": "optional"
                  },
                  "host_name": {
                    "type": "string",
                    "description": "optional"
                  },
                  "host_email": {
                    "type": "string",
                    "description": "optional but recommended; receives a magic-link backup login"
                  }
                }
              },
              "example": {
                "title": "Brenda's Baby Shower",
                "starts_at": "2026-06-15T14:00:00Z",
                "location": "Central Park",
                "host_name": "Sarah",
                "host_email": "sarah@example.com"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK"
          },
          "400": {
            "description": "Bad request"
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/api/events/{slug}": {
      "get": {
        "summary": "Get public event details and RSVP counts. No auth.",
        "operationId": "get_api_events_slug",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          },
          "400": {
            "description": "Bad request"
          },
          "404": {
            "description": "Not found"
          }
        }
      },
      "patch": {
        "summary": "PATCH equivalent of /api/events/{slug}/update for traditional REST clients. Body is JSON. Agents should prefer the GET form.",
        "operationId": "patch_api_events_slug",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "t",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "edit_token (alternative to Authorization header)"
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "title": {
                    "type": "string",
                    "description": "optional"
                  },
                  "description": {
                    "type": "string",
                    "description": "optional"
                  },
                  "starts_at": {
                    "type": "string",
                    "description": "optional"
                  },
                  "ends_at": {
                    "type": "string",
                    "description": "optional"
                  },
                  "location": {
                    "type": "string",
                    "description": "optional"
                  },
                  "host_name": {
                    "type": "string",
                    "description": "optional"
                  },
                  "host_email": {
                    "type": "string",
                    "description": "optional"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          }
        }
      },
      "delete": {
        "summary": "DELETE equivalent of /api/events/{slug}/delete for traditional REST clients. Agents should prefer the GET form.",
        "operationId": "delete_api_events_slug",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "t",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "edit_token (alternative to Authorization header)"
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/api/events/{slug}/update": {
      "get": {
        "summary": "Update an event. Pass changed fields as query params. Authenticate with ?t={edit_token}.",
        "operationId": "get_api_events_slug_update",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "title",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "optional"
          },
          {
            "name": "description",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "optional"
          },
          {
            "name": "starts_at",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "optional"
          },
          {
            "name": "ends_at",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "optional"
          },
          {
            "name": "location",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "optional"
          },
          {
            "name": "host_name",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "optional"
          },
          {
            "name": "host_email",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "optional"
          },
          {
            "name": "t",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "edit_token (alternative to Authorization header)"
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/api/events/{slug}/delete": {
      "get": {
        "summary": "Cancel/delete an event and all its RSVPs. Authenticate with ?t={edit_token}.",
        "operationId": "get_api_events_slug_delete",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "t",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "edit_token (alternative to Authorization header)"
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/api/events/{slug}/rsvps": {
      "get": {
        "summary": "List all RSVPs for the event. Auth required.",
        "operationId": "get_api_events_slug_rsvps",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "t",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "edit_token (alternative to Authorization header)"
          }
        ],
        "security": [
          {
            "bearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          },
          "400": {
            "description": "Bad request"
          },
          "401": {
            "description": "Unauthorized"
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/api/auth/magic": {
      "get": {
        "summary": "Email a fresh magic-link to the host. Use when the host has lost their edit_token. Always returns ok to avoid leaking event existence.",
        "operationId": "get_api_auth_magic",
        "parameters": [
          {
            "name": "slug",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "required"
          },
          {
            "name": "host_email",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "required, must match the email on file"
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          },
          "400": {
            "description": "Bad request"
          },
          "404": {
            "description": "Not found"
          }
        }
      },
      "post": {
        "summary": "POST equivalent of /api/auth/magic for traditional REST clients. Body is JSON. Agents should prefer the GET form.",
        "operationId": "post_api_auth_magic",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "slug": {
                    "type": "string",
                    "description": "required"
                  },
                  "host_email": {
                    "type": "string",
                    "description": "required, must match the email on file"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK"
          },
          "400": {
            "description": "Bad request"
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    }
  }
}