{
  "openapi": "3.1.0",
  "info": {
    "title": "Crash Override Public Website API",
    "version": "1.0.0",
    "description": "The public HTTP API behind crashoverride.com — site search, contact and newsletter forms, event registration, and open-source binary downloads. All endpoints are unauthenticated; write endpoints are protected by an Origin same-origin check and per-IP rate limits (429 responses carry no Retry-After header). Errors are always JSON objects with an `error` field. Every HTML page on the site also serves a markdown rendition via `Accept: text/markdown` content negotiation, and a machine-readable site overview lives at https://crashoverride.com/llms.txt.",
    "contact": {
      "name": "Crash Override",
      "email": "hello@crashoverride.com",
      "url": "https://crashoverride.com/contact"
    }
  },
  "externalDocs": {
    "description": "LLM-readable site overview (llms.txt)",
    "url": "https://crashoverride.com/llms.txt"
  },
  "servers": [
    {
      "url": "https://crashoverride.com",
      "description": "Production"
    }
  ],
  "tags": [
    {
      "name": "search",
      "description": "Site-wide content search"
    },
    {
      "name": "contact",
      "description": "Contact form"
    },
    {
      "name": "newsletter",
      "description": "Newsletter double-opt-in lifecycle"
    },
    {
      "name": "events",
      "description": "Event RSVP and interest registration"
    },
    {
      "name": "downloads",
      "description": "Open-source binary download redirects"
    }
  ],
  "paths": {
    "/api/search": {
      "get": {
        "operationId": "searchSite",
        "summary": "Search site content",
        "description": "Full-text search across knowledge-base articles, blog posts, marketing pages, glossary terms, docs and help pages. Queries shorter than 2 characters (after sanitisation) and queries that look like an email address or phone number return an empty result set. Rate limited to 30 requests per IP per minute.",
        "tags": [
          "search"
        ],
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Search query, 2–100 characters.",
            "schema": {
              "type": "string",
              "minLength": 2,
              "maxLength": 100
            }
          },
          {
            "name": "types",
            "in": "query",
            "required": false,
            "description": "Comma-separated content-type filter. Valid entries: kb_article, blog_post, marketing_page, glossary_term, docs_page, help_page.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum results (1–50, default 20).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50,
              "default": 20
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Search results (possibly empty).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SearchResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Search backend failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Search not configured.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/contact/csrf": {
      "get": {
        "operationId": "getContactCsrfToken",
        "summary": "Issue the CSRF token for the contact form",
        "description": "Returns a fresh CSRF token and sets the matching signed `co_csrf` cookie. Call this first, then submit the token as the `csrf` field of POST /api/contact-submit while carrying the cookie (double-submit pattern). Responses are uncacheable (`Cache-Control: no-store, private`).",
        "tags": [
          "contact"
        ],
        "responses": {
          "200": {
            "description": "Token issued; `Set-Cookie: co_csrf=…` accompanies it.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "token": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "token"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/api/contact-submit": {
      "post": {
        "operationId": "submitContactForm",
        "summary": "Submit the contact form",
        "description": "Browser-context endpoint: the server enforces a same-origin check, so the request must carry an `Origin: https://crashoverride.com` header or it is rejected with 403. Additionally requires the CSRF double-submit pair from GET /api/contact/csrf (cookie + `csrf` body field). The `pot` field is a honeypot: it must be present and empty. All failures return the generic body `{\"error\":\"generic_failure\"}`. Rate limit: 5 requests per IP per 10 minutes.",
        "tags": [
          "contact"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "handle": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 64,
                    "description": "Name or handle."
                  },
                  "email": {
                    "type": "string",
                    "format": "email",
                    "maxLength": 256
                  },
                  "intent": {
                    "type": "string",
                    "enum": [
                      "demo",
                      "question",
                      "partnership",
                      "press"
                    ],
                    "description": "Why you are getting in touch."
                  },
                  "message": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 4000
                  },
                  "csrf": {
                    "type": "string",
                    "minLength": 1,
                    "description": "Token from GET /api/contact/csrf."
                  },
                  "heard_about": {
                    "type": "string",
                    "maxLength": 300
                  },
                  "pot": {
                    "type": "string",
                    "maxLength": 4000,
                    "description": "Honeypot — must be an empty string."
                  }
                },
                "required": [
                  "handle",
                  "email",
                  "intent",
                  "message",
                  "csrf",
                  "pot"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Accepted.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    }
                  },
                  "required": [
                    "ok"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON, failed validation, or disposable email domain.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Origin check or CSRF verification failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited (5 per IP per 10 minutes).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/newsletter/subscribe": {
      "post": {
        "operationId": "subscribeNewsletter",
        "summary": "Start a newsletter double-opt-in subscription",
        "description": "Browser-context endpoint: the server enforces a same-origin check, so the request must carry an `Origin: https://crashoverride.com` header or it is rejected with 403. Starts the double-opt-in flow: a confirmation email is sent and the subscription stays pending until the recipient follows the emailed link (GET /api/newsletter/confirm, which then asks them to confirm with a button — following the link alone no longer completes it). Disposable email domains are rejected. Rate limit: 3 requests per IP per hour.",
        "tags": [
          "newsletter"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email",
                    "maxLength": 254
                  },
                  "sourcePage": {
                    "type": "string",
                    "description": "Same-origin path the signup came from."
                  },
                  "utmSource": {
                    "type": "string"
                  },
                  "utmMedium": {
                    "type": "string"
                  },
                  "utmCampaign": {
                    "type": "string"
                  }
                },
                "required": [
                  "email"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Confirmation email queued; subscription pending.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    },
                    "pending": {
                      "type": "boolean",
                      "const": true
                    }
                  },
                  "required": [
                    "ok",
                    "pending"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON, invalid email, or disposable email domain.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Origin check failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited (3 per IP per hour).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/newsletter/confirm": {
      "get": {
        "operationId": "reviewNewsletterConfirmation",
        "summary": "Check a confirmation link and choose which page to show",
        "description": "Target of the double-opt-in confirmation link. `token` is an HMAC-signed token with a 7-day TTL, minted by the subscribe flow — it cannot be constructed by a client. **Mutates nothing**: it verifies the token and picks a destination, so a link-scanner prefetch has no effect. Always answers with a 302 redirect: to /newsletter/confirmed?state=review&token=… for a valid token — a page carrying a confirm button that POSTs back to this same path — or /newsletter/confirmed?state=invalid for a missing/expired/forged token. Never 500s.",
        "tags": [
          "newsletter"
        ],
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "description": "Signed confirmation token from the email.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "302": {
            "description": "Redirect to the review page (state=review) or the invalid page (state=invalid).",
            "headers": {
              "Location": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "confirmNewsletterSubscription",
        "summary": "Confirm a newsletter subscription (the button click)",
        "description": "Performs the confirmation — the click, not the fetch, is the consent. Submitted as a native HTML form from /newsletter/confirmed?state=review. Runs the fan-out (consent_log, Customer.io event, welcome email, unsub_token, Slack alert; no Attio — newsletter is deliberately kept out of it) and is idempotent: a second confirm for an already-opted-in address skips the fan-out. Guarded by a same-origin check in addition to the token. Answers 303, to /newsletter/confirmed?state=confirmed, or to the event leaderboard when the token carries an arcade claim.",
        "tags": [
          "newsletter"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "required": [
                  "token"
                ],
                "properties": {
                  "token": {
                    "type": "string",
                    "description": "Signed confirmation token, echoed from the review page."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "303": {
            "description": "Redirect to the confirmation result page: state=confirmed on success, state=invalid for a bad token, or state=blocked when the origin check refuses the request.",
            "headers": {
              "Location": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "403": {
            "description": "Refused by Astro's built-in security.checkOrigin, which runs before this route and answers plainly cross-origin or missing-Origin form POSTs. The body is plain text, not this API's JSON error shape.",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string",
                  "examples": [
                    "Cross-site POST form submissions are forbidden"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/api/newsletter/unsubscribe": {
      "post": {
        "operationId": "unsubscribeNewsletter",
        "summary": "Unsubscribe from the newsletter (emailed link token)",
        "description": "Browser-context endpoint: the server enforces a same-origin check, so the request must carry an `Origin: https://crashoverride.com` header or it is rejected with 403. Accepts JSON or form-encoded bodies. The token is a signed, purpose-scoped unsubscribe token from newsletter emails. On success or invalid token the response is a 302 redirect to /newsletter/unsubscribe?state=done or ?state=invalid. Rate limit: 10 requests per IP per minute.",
        "tags": [
          "newsletter"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "token": {
                    "type": "string"
                  }
                },
                "required": [
                  "token"
                ]
              }
            },
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "properties": {
                  "token": {
                    "type": "string"
                  }
                },
                "required": [
                  "token"
                ]
              }
            }
          }
        },
        "responses": {
          "302": {
            "description": "Redirect to the unsubscribe result page (state=done or state=invalid).",
            "headers": {
              "Location": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "403": {
            "description": "Origin check failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited (10 per IP per minute).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/events/rsvp": {
      "post": {
        "operationId": "rsvpToEvent",
        "summary": "RSVP to a published event",
        "description": "Browser-context endpoint: the server enforces a same-origin check, so the request must carry an `Origin: https://crashoverride.com` header or it is rejected with 403. Registers an attendee for a published, listed event. Some events additionally require last_name and job_title (when the event collects attendee details) or an access code (rsvp_code). Rate limit: 5 requests per IP per 10 minutes.",
        "tags": [
          "events"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "event_slug": {
                    "type": "string",
                    "maxLength": 200
                  },
                  "first_name": {
                    "type": "string",
                    "maxLength": 80
                  },
                  "last_name": {
                    "type": "string",
                    "maxLength": 80,
                    "description": "Required when the event collects attendee details."
                  },
                  "job_title": {
                    "type": "string",
                    "maxLength": 120,
                    "description": "Required when the event collects attendee details."
                  },
                  "email": {
                    "type": "string",
                    "format": "email",
                    "maxLength": 254
                  },
                  "company": {
                    "type": "string",
                    "maxLength": 120
                  },
                  "rsvp_code": {
                    "type": "string",
                    "description": "Access code — required only for code-gated events."
                  },
                  "newsletter_consent": {
                    "type": "boolean",
                    "description": "Explicit newsletter opt-in; only literal true counts."
                  }
                },
                "required": [
                  "event_slug",
                  "first_name",
                  "email",
                  "company"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Registered (status=registered) or queued (status=pending).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RsvpResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON or a missing/invalid required field.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Access code required or invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Origin check failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Event not found or not open for RSVP.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Already registered, or event at capacity.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "410": {
            "description": "Event has ended.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited (5 per IP per 10 minutes).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Registration failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Upstream registration provider rejected the request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Registration temporarily unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/events/register-interest": {
      "post": {
        "operationId": "registerEventInterest",
        "summary": "Register interest in an event without a date",
        "description": "Browser-context endpoint: the server enforces a same-origin check, so the request must carry an `Origin: https://crashoverride.com` header or it is rejected with 403. Records an email to notify when an event is scheduled. Only available for published events that accept interest registration. Rate limit: 5 requests per IP per 10 minutes.",
        "tags": [
          "events"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "event_slug": {
                    "type": "string",
                    "maxLength": 200
                  },
                  "email": {
                    "type": "string",
                    "format": "email",
                    "maxLength": 254
                  },
                  "first_name": {
                    "type": "string",
                    "maxLength": 80
                  }
                },
                "required": [
                  "event_slug",
                  "email"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Interest recorded.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    },
                    "message": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "ok",
                    "message"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON or a missing/invalid required field.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Origin check failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Event not found or not accepting interest registration.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "410": {
            "description": "Event has ended.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited (5 per IP per 10 minutes).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Could not register interest.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Service unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/download/{source}/{platform}": {
      "get": {
        "operationId": "downloadBinary",
        "summary": "Download an open-source tool binary",
        "description": "Redirects (302) to the CDN artifact for the requested tool and platform. Unauthenticated. Error responses are JSON with `Cache-Control: no-store`. Rate limit: 20 requests per IP per minute.",
        "tags": [
          "downloads"
        ],
        "parameters": [
          {
            "name": "source",
            "in": "path",
            "required": true,
            "description": "Tool identifier from the download registry (e.g. a Crash Override OSS tool).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "platform",
            "in": "path",
            "required": true,
            "description": "Target platform.",
            "schema": {
              "type": "string",
              "enum": [
                "macos-arm64",
                "macos-x64",
                "windows",
                "windows-arm64",
                "linux"
              ]
            }
          }
        ],
        "responses": {
          "302": {
            "description": "Redirect to the binary artifact URL.",
            "headers": {
              "Location": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "description": "Unknown source or platform.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Downloads disabled for this tool.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Download not available for this source/platform.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited (20 per IP per minute).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Error": {
        "type": "object",
        "description": "Standard error envelope. `error` is a short machine-readable code or message; middleware-generated errors additionally carry message, status, path and hint.",
        "properties": {
          "error": {
            "type": "string"
          },
          "message": {
            "type": "string"
          },
          "status": {
            "type": "integer"
          },
          "path": {
            "type": "string"
          },
          "hint": {
            "type": "string"
          }
        },
        "required": [
          "error"
        ]
      },
      "SearchResult": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "kb_article",
              "blog_post",
              "marketing_page",
              "glossary_term",
              "docs_page",
              "help_page"
            ]
          },
          "title": {
            "type": "string"
          },
          "url": {
            "type": "string"
          },
          "excerpt": {
            "type": [
              "string",
              "null"
            ]
          },
          "rank": {
            "type": "number"
          }
        },
        "required": [
          "type",
          "title",
          "url",
          "excerpt",
          "rank"
        ]
      },
      "SearchResponse": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          },
          "query": {
            "type": "string"
          },
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SearchResult"
            }
          },
          "did_you_mean": {
            "type": "string",
            "description": "Present only when fuzzy matching found an alternative title."
          }
        },
        "required": [
          "ok",
          "query",
          "results"
        ]
      },
      "RsvpResponse": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          },
          "status": {
            "type": "string",
            "enum": [
              "registered",
              "pending"
            ]
          },
          "message": {
            "type": "string"
          },
          "ics_url": {
            "type": "string"
          },
          "add_to_calendar_links": {
            "type": "object",
            "properties": {
              "google": {
                "type": "string"
              },
              "outlook": {
                "type": "string"
              },
              "ical": {
                "type": "string"
              },
              "yahoo": {
                "type": "string"
              }
            }
          }
        },
        "required": [
          "ok",
          "status"
        ]
      }
    }
  }
}