{"openapi":"3.1.0","info":{"title":"Funnel Mage","version":"1.0.0","summary":"Human checkout for an automated funnel report. Not a partner platform.","description":"Funnel Mage has no OAuth authorization server and no long-lived API keys. Checkout and the JSON index are zero-auth (auth: none). There is no contact-sales form. A human pays via Stripe Checkout. The unguessable run UUID in the success URL is the only credential for reading that run (scope: run:read on that id only). Free sandbox: GET /sandbox (alias of /sample-report).\n\nVersioning: v1 lives at /api/v1/* and /v1/*. Unversioned /api/* is a permanent alias of v1. Breaking changes will ship as /api/v2 and /v2. Before removing v1 we will send Deprecation (RFC 9745) and Sunset (RFC 8594) response headers at least 90 days in advance. Those headers are absent today because v1 is current. Responses always include API-Version: 1. Verify the surface with GET /api/v1 (no credentials) or GET /api/v1/checkout (typed 405).","contact":{"email":"ian@lazige.agency","url":"https://funnelmage.lazige.agency/contact"}},"servers":[{"url":"https://funnelmage.lazige.agency","description":"This deployment. Canonical paths are /api/v1/*; /v1/* and /api/* are aliases."}],"tags":[{"name":"discovery","description":"Zero-auth catalog. No API key."},{"name":"checkout","description":"Start a paid report. Returns a Stripe Checkout URL."},{"name":"runs","description":"Read a run you already have the UUID for."}],"paths":{"/api/v1":{"get":{"operationId":"getApiIndex","tags":["discovery"],"summary":"Zero-auth API index","description":"No API key, no OAuth, no sales form. Returns auth: none, versioning, OpenAPI, MCP, and the free sandbox URL. Call this with no credentials to verify the public surface.","security":[],"parameters":[{"$ref":"#/components/parameters/ApiVersion"}],"responses":{"200":{"description":"API catalog","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiIndex"}}}},"default":{"$ref":"#/components/responses/Error"}}}},"/api/v1/checkout":{"get":{"operationId":"probeCheckout","tags":["checkout"],"summary":"Checkout is POST only","description":"GET returns 405 with a typed RFC 9457 error and Allow: POST. Use this to verify the error model without creating a session.","security":[],"parameters":[{"$ref":"#/components/parameters/ApiVersion"}],"responses":{"405":{"$ref":"#/components/responses/Error"},"default":{"$ref":"#/components/responses/Error"}}},"post":{"operationId":"createCheckout","tags":["checkout"],"summary":"Create a run and a Stripe Checkout session","description":"Validates the target URL, prices the selected checks server-side, and returns a Stripe Checkout URL for a human to pay. No authentication. Rate-limited per IP. Unversioned POST /api/checkout is a permanent alias.","security":[],"parameters":[{"$ref":"#/components/parameters/ApiVersion"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckoutRequest"}}}},"responses":{"200":{"description":"Checkout session created","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckoutResponse"}}}},"400":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/Error"},"503":{"$ref":"#/components/responses/Error"},"default":{"$ref":"#/components/responses/Error"}}}},"/api/v1/runs/{id}":{"get":{"operationId":"getRun","tags":["runs"],"summary":"Poll run status","description":"Returns the run, its stages, and whether the report files exist. Possessing the UUID is the credential (scope run:read).","security":[{"runRead":[]},{"oauth2":["run:read"]}],"parameters":[{"$ref":"#/components/parameters/RunId"},{"$ref":"#/components/parameters/ApiVersion"}],"responses":{"200":{"description":"Run payload","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RunPayload"}}}},"404":{"$ref":"#/components/responses/Error"},"500":{"$ref":"#/components/responses/Error"},"503":{"$ref":"#/components/responses/Error"},"default":{"$ref":"#/components/responses/Error"}}}},"/api/v1/runs/{id}/report":{"get":{"operationId":"getRunReport","tags":["runs"],"summary":"Download the finished report","description":"HTML by default; set format=pdf for the PDF. Only complete runs.","security":[{"runRead":[]},{"oauth2":["run:read"]}],"parameters":[{"$ref":"#/components/parameters/RunId"},{"$ref":"#/components/parameters/ApiVersion"},{"name":"format","in":"query","schema":{"type":"string","enum":["html","pdf"]},"description":"File type. Default html."}],"responses":{"200":{"description":"Report bytes (HTML or PDF)","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}}},"404":{"$ref":"#/components/responses/Error"},"409":{"$ref":"#/components/responses/Error"},"500":{"$ref":"#/components/responses/Error"},"503":{"$ref":"#/components/responses/Error"},"default":{"$ref":"#/components/responses/Error"}}}},"/api/v1/runs/{id}/email":{"post":{"operationId":"emailRunReport","tags":["runs"],"summary":"Re-send the report link to the address already on file","description":"Does not accept a recipient in the body. A leaked UUID cannot be turned into an email relay.","security":[{"runRead":[]},{"oauth2":["run:read"]}],"parameters":[{"$ref":"#/components/parameters/RunId"},{"$ref":"#/components/parameters/ApiVersion"}],"responses":{"200":{"description":"Sent","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"type":"object","properties":{"sent":{"type":"boolean"}},"required":["sent"]}}}},"404":{"$ref":"#/components/responses/Error"},"409":{"$ref":"#/components/responses/Error"},"422":{"$ref":"#/components/responses/Error"},"500":{"$ref":"#/components/responses/Error"},"502":{"$ref":"#/components/responses/Error"},"503":{"$ref":"#/components/responses/Error"},"default":{"$ref":"#/components/responses/Error"}}}},"/api":{"get":{"operationId":"getApiIndexAlias","tags":["discovery"],"summary":"Zero-auth API index","description":"No API key, no OAuth, no sales form. Returns auth: none, versioning, OpenAPI, MCP, and the free sandbox URL. Call this with no credentials to verify the public surface. Unversioned alias of the /api/v1 path. Same contract.","security":[],"parameters":[{"$ref":"#/components/parameters/ApiVersion"}],"responses":{"200":{"description":"API catalog","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiIndex"}}}},"default":{"$ref":"#/components/responses/Error"}}}},"/v1":{"get":{"operationId":"getApiIndexBare","tags":["discovery"],"summary":"Zero-auth API index","description":"No API key, no OAuth, no sales form. Returns auth: none, versioning, OpenAPI, MCP, and the free sandbox URL. Call this with no credentials to verify the public surface. Short /v1 alias of the /api/v1 path. Same contract.","security":[],"parameters":[{"$ref":"#/components/parameters/ApiVersion"}],"responses":{"200":{"description":"API catalog","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiIndex"}}}},"default":{"$ref":"#/components/responses/Error"}}}},"/api/checkout":{"get":{"operationId":"probeCheckoutAlias","tags":["checkout"],"summary":"Checkout is POST only","description":"GET returns 405 with a typed RFC 9457 error and Allow: POST. Use this to verify the error model without creating a session. Unversioned alias of the /api/v1 path. Same contract.","security":[],"parameters":[{"$ref":"#/components/parameters/ApiVersion"}],"responses":{"405":{"$ref":"#/components/responses/Error"},"default":{"$ref":"#/components/responses/Error"}}},"post":{"operationId":"createCheckoutAlias","tags":["checkout"],"summary":"Create a run and a Stripe Checkout session","description":"Validates the target URL, prices the selected checks server-side, and returns a Stripe Checkout URL for a human to pay. No authentication. Rate-limited per IP. Unversioned POST /api/checkout is a permanent alias. Unversioned alias of the /api/v1 path. Same contract.","security":[],"parameters":[{"$ref":"#/components/parameters/ApiVersion"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckoutRequest"}}}},"responses":{"200":{"description":"Checkout session created","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckoutResponse"}}}},"400":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/Error"},"503":{"$ref":"#/components/responses/Error"},"default":{"$ref":"#/components/responses/Error"}}}},"/v1/checkout":{"get":{"operationId":"probeCheckoutBare","tags":["checkout"],"summary":"Checkout is POST only","description":"GET returns 405 with a typed RFC 9457 error and Allow: POST. Use this to verify the error model without creating a session. Short /v1 alias of the /api/v1 path. Same contract.","security":[],"parameters":[{"$ref":"#/components/parameters/ApiVersion"}],"responses":{"405":{"$ref":"#/components/responses/Error"},"default":{"$ref":"#/components/responses/Error"}}},"post":{"operationId":"createCheckoutBare","tags":["checkout"],"summary":"Create a run and a Stripe Checkout session","description":"Validates the target URL, prices the selected checks server-side, and returns a Stripe Checkout URL for a human to pay. No authentication. Rate-limited per IP. Unversioned POST /api/checkout is a permanent alias. Short /v1 alias of the /api/v1 path. Same contract.","security":[],"parameters":[{"$ref":"#/components/parameters/ApiVersion"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckoutRequest"}}}},"responses":{"200":{"description":"Checkout session created","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckoutResponse"}}}},"400":{"$ref":"#/components/responses/Error"},"429":{"$ref":"#/components/responses/RateLimited"},"500":{"$ref":"#/components/responses/Error"},"503":{"$ref":"#/components/responses/Error"},"default":{"$ref":"#/components/responses/Error"}}}},"/api/runs/{id}":{"get":{"operationId":"getRunAlias","tags":["runs"],"summary":"Poll run status","description":"Returns the run, its stages, and whether the report files exist. Possessing the UUID is the credential (scope run:read). Unversioned alias of the /api/v1 path. Same contract.","security":[{"runRead":[]},{"oauth2":["run:read"]}],"parameters":[{"$ref":"#/components/parameters/RunId"},{"$ref":"#/components/parameters/ApiVersion"}],"responses":{"200":{"description":"Run payload","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RunPayload"}}}},"404":{"$ref":"#/components/responses/Error"},"500":{"$ref":"#/components/responses/Error"},"503":{"$ref":"#/components/responses/Error"},"default":{"$ref":"#/components/responses/Error"}}}},"/v1/runs/{id}":{"get":{"operationId":"getRunBare","tags":["runs"],"summary":"Poll run status","description":"Returns the run, its stages, and whether the report files exist. Possessing the UUID is the credential (scope run:read). Short /v1 alias of the /api/v1 path. Same contract.","security":[{"runRead":[]},{"oauth2":["run:read"]}],"parameters":[{"$ref":"#/components/parameters/RunId"},{"$ref":"#/components/parameters/ApiVersion"}],"responses":{"200":{"description":"Run payload","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RunPayload"}}}},"404":{"$ref":"#/components/responses/Error"},"500":{"$ref":"#/components/responses/Error"},"503":{"$ref":"#/components/responses/Error"},"default":{"$ref":"#/components/responses/Error"}}}},"/api/runs/{id}/report":{"get":{"operationId":"getRunReportAlias","tags":["runs"],"summary":"Download the finished report","description":"HTML by default; set format=pdf for the PDF. Only complete runs. Unversioned alias of the /api/v1 path. Same contract.","security":[{"runRead":[]},{"oauth2":["run:read"]}],"parameters":[{"$ref":"#/components/parameters/RunId"},{"$ref":"#/components/parameters/ApiVersion"},{"name":"format","in":"query","schema":{"type":"string","enum":["html","pdf"]},"description":"File type. Default html."}],"responses":{"200":{"description":"Report bytes (HTML or PDF)","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}}},"404":{"$ref":"#/components/responses/Error"},"409":{"$ref":"#/components/responses/Error"},"500":{"$ref":"#/components/responses/Error"},"503":{"$ref":"#/components/responses/Error"},"default":{"$ref":"#/components/responses/Error"}}}},"/v1/runs/{id}/report":{"get":{"operationId":"getRunReportBare","tags":["runs"],"summary":"Download the finished report","description":"HTML by default; set format=pdf for the PDF. Only complete runs. Short /v1 alias of the /api/v1 path. Same contract.","security":[{"runRead":[]},{"oauth2":["run:read"]}],"parameters":[{"$ref":"#/components/parameters/RunId"},{"$ref":"#/components/parameters/ApiVersion"},{"name":"format","in":"query","schema":{"type":"string","enum":["html","pdf"]},"description":"File type. Default html."}],"responses":{"200":{"description":"Report bytes (HTML or PDF)","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}}},"404":{"$ref":"#/components/responses/Error"},"409":{"$ref":"#/components/responses/Error"},"500":{"$ref":"#/components/responses/Error"},"503":{"$ref":"#/components/responses/Error"},"default":{"$ref":"#/components/responses/Error"}}}},"/api/runs/{id}/email":{"post":{"operationId":"emailRunReportAlias","tags":["runs"],"summary":"Re-send the report link to the address already on file","description":"Does not accept a recipient in the body. A leaked UUID cannot be turned into an email relay. Unversioned alias of the /api/v1 path. Same contract.","security":[{"runRead":[]},{"oauth2":["run:read"]}],"parameters":[{"$ref":"#/components/parameters/RunId"},{"$ref":"#/components/parameters/ApiVersion"}],"responses":{"200":{"description":"Sent","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"type":"object","properties":{"sent":{"type":"boolean"}},"required":["sent"]}}}},"404":{"$ref":"#/components/responses/Error"},"409":{"$ref":"#/components/responses/Error"},"422":{"$ref":"#/components/responses/Error"},"500":{"$ref":"#/components/responses/Error"},"502":{"$ref":"#/components/responses/Error"},"503":{"$ref":"#/components/responses/Error"},"default":{"$ref":"#/components/responses/Error"}}}},"/v1/runs/{id}/email":{"post":{"operationId":"emailRunReportBare","tags":["runs"],"summary":"Re-send the report link to the address already on file","description":"Does not accept a recipient in the body. A leaked UUID cannot be turned into an email relay. Short /v1 alias of the /api/v1 path. Same contract.","security":[{"runRead":[]},{"oauth2":["run:read"]}],"parameters":[{"$ref":"#/components/parameters/RunId"},{"$ref":"#/components/parameters/ApiVersion"}],"responses":{"200":{"description":"Sent","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"type":"object","properties":{"sent":{"type":"boolean"}},"required":["sent"]}}}},"404":{"$ref":"#/components/responses/Error"},"409":{"$ref":"#/components/responses/Error"},"422":{"$ref":"#/components/responses/Error"},"500":{"$ref":"#/components/responses/Error"},"502":{"$ref":"#/components/responses/Error"},"503":{"$ref":"#/components/responses/Error"},"default":{"$ref":"#/components/responses/Error"}}}}},"components":{"securitySchemes":{"runRead":{"type":"apiKey","in":"path","name":"id","description":"Scope run:read. The unguessable run UUID is the capability token. There is no OAuth, bearer token, or dashboard API key."},"oauth2":{"type":"oauth2","description":"Named scopes for least-privilege. Funnel Mage does not issue OAuth tokens. run:read is granted by possessing the run UUID (see runRead). POST to the tokenUrl returns JSON explaining that.","flows":{"clientCredentials":{"tokenUrl":"https://funnelmage.lazige.agency/api/v1","scopes":{"run:read":"Read one run and its report. Granted by possessing that run's unguessable UUID."}}}}},"parameters":{"RunId":{"name":"id","in":"path","required":true,"description":"Run UUID from the Stripe success URL.","schema":{"type":"string","format":"uuid"}},"ApiVersion":{"name":"API-Version","in":"header","required":false,"description":"Optional request hint. Responses always send API-Version: 1.","schema":{"type":"string","enum":["1"]}}},"headers":{"ApiVersion":{"description":"Current API version for this response.","schema":{"type":"string","enum":["1"]}},"Deprecation":{"description":"RFC 9745. Sent when an endpoint is deprecated; absent while v1 is current.","schema":{"type":"string"}},"Sunset":{"description":"RFC 8594 HTTP-date after which the deprecated version may be removed. At least 90 days after Deprecation.","schema":{"type":"string"}}},"schemas":{"ApiIndex":{"type":"object","required":["name","version","auth","api_keys","contact_sales","sandbox","openapi","mcp"],"properties":{"name":{"type":"string","const":"Funnel Mage"},"version":{"type":"string","const":"1"},"auth":{"type":"string","const":"none"},"authentication":{"type":"string","const":"none"},"api_keys":{"type":"boolean","const":false},"oauth":{"type":"boolean","const":false},"contact_sales":{"type":"boolean","const":false},"self_serve":{"type":"boolean","const":true},"sandbox":{"type":"string","format":"uri"},"sample_report":{"type":"string","format":"uri"},"openapi":{"type":"string","format":"uri"},"mcp":{"type":"string","format":"uri"}}},"CheckoutRequest":{"type":"object","required":["company","url","problem_statement","modules"],"properties":{"company":{"type":"string","maxLength":200},"url":{"type":"string","format":"uri"},"problem_statement":{"type":"string","minLength":10,"maxLength":4000},"modules":{"type":"array","minItems":1,"items":{"type":"string","enum":["seo","paid_landing","competitors","cwv","copy"]}},"contact_email":{"type":"string","format":"email"},"ad_copy":{"type":"string"},"ad_spend_band":{"type":"string"},"traffic_sources":{"type":"array","items":{"type":"string"}},"conversion_goal":{"type":"string"}}},"CheckoutResponse":{"type":"object","required":["runId","checkoutUrl"],"properties":{"runId":{"type":"string","format":"uuid"},"checkoutUrl":{"type":"string","format":"uri"}}},"RunPayload":{"type":"object","required":["run","stages","report"],"properties":{"run":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"status":{"type":"string"},"company":{"type":"string"},"refunded":{"type":"boolean"}},"additionalProperties":true},"stages":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"status":{"type":"string"}},"additionalProperties":true}},"report":{"type":["object","null"],"properties":{"htmlReady":{"type":"boolean"},"pdfReady":{"type":"boolean"},"criticPassed":{"type":"boolean"},"criticAttempts":{"type":"integer"}}}}},"Error":{"type":"object","required":["type","title","status","detail","error","code","message","hint"],"properties":{"type":{"type":"string","format":"uri","description":"RFC 9457 problem type."},"title":{"type":"string"},"status":{"type":"integer"},"detail":{"type":"string"},"error":{"type":"string","description":"Same text as title; kept for existing clients."},"code":{"type":"string"},"message":{"type":"string"},"hint":{"type":"string"}}}},"responses":{"Error":{"description":"Structured JSON error (RFC 9457 fields plus a string error for existing clients).","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}},"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Too many checkout attempts from this IP","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait"},"RateLimit":{"schema":{"type":"string"}},"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Error"}},"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}