{"openapi":"3.1.0","info":{"title":"Flaunt Data API","version":"1.0.0","description":"Programmatic, **read-only** access to your Flaunt data warehouse\n\n### Authentication\nSend your API key as a Bearer token: `Authorization: Bearer flnt_live_…`.\nA key is scoped to **either** an organization (all its brands) **or** a single brand.\n\n### Getting an API key\nIn your Flaunt admin, open **API Keys** (in your organization or brand settings), click\n**Create key**, give it a name, and copy the secret — it's shown only once, so store it\nsomewhere safe. You can revoke a key any time from the same screen.\n\n![The API Keys screen in the Flaunt admin](/img/api-keys.png)\n\n### Typical flow\n1. `GET /v1/data/brands` — see which brands your key can access (each with its `dataset`).\n2. `POST /v1/data/query` — run a read-only `SELECT`/`WITH` query, referencing tables as `` `flaunt-v1.<dataset>.<table>` ``.\n\n### Build from a proven query\nSkip writing SQL — reuse queries already validated inside Flaunt:\n1. `GET /v1/data/reports` — the reports (curated, repeatable definitions) your key can see.\n2. `GET /v1/data/reports/{reportId}/queries` — that report's queries; or `GET /v1/data/queries` for standalone saved queries.\n3. `POST /v1/data/query` with `{ \"queryId\": \"…\" }` — run one by id. For a parameterized (report) query, add a `period` (e.g. `{ \"cadence\": \"MONTHLY\" }`) to get a fresh window each run.\n\n### Discovering tables\nThere's no separate schema endpoint — introspect with a query:\n```sql\nSELECT table_name, layer, data_source FROM `flaunt-v1.<dataset>.table_metadata`;\n```\nPass `\"dryRun\": true` to estimate bytes scanned before running an expensive query.\n\n### MCP server (for AI agents)\nEvery endpoint below is also exposed over the **Model Context Protocol** at\n`https://api.flaunt.xyz/v1/data/mcp` (Streamable HTTP). Authenticate one of two ways: **OAuth**\nfor **claude.ai** custom connectors (no key to manage — see *Connecting claude.ai* below), or the\n**same API key** as a Bearer token for **Claude Code**, Claude Desktop, and headless clients.\n\nIn **Claude Code** (API key):\n```bash\nclaude mcp add --transport http flaunt https://api.flaunt.xyz/v1/data/mcp \\\n  --header \"Authorization: Bearer $FLAUNT_KEY\"\n```\nIn **Claude Desktop** (native connector GUI is OAuth-only, so bridge with `mcp-remote`), open\n**Settings → Developer → Edit Config** and add to `claude_desktop_config.json`:\n```json\n\"mcpServers\": { \"flaunt\": {\n  \"command\": \"npx\",\n  \"args\": [\"-y\", \"mcp-remote\", \"https://api.flaunt.xyz/v1/data/mcp\", \"--header\", \"Authorization:${FLAUNT_AUTH}\"],\n  \"env\": { \"FLAUNT_AUTH\": \"Bearer flnt_live_…\" }\n} }\n```\nThe token goes in an `env` var because Claude Desktop splits each `args` entry on spaces; the\nspace-free `Authorization:${FLAUNT_AUTH}` avoids a truncated header. Restart Desktop afterward.\nTools mirror the REST operations 1:1: `list_brands`, `list_reports`, `list_report_queries`,\n`list_saved_queries`, `run_query`, and `report_data_issue`. The same scoping, hardening, and\ndaily usage limit apply.\n### Call outs for Claude Desktop\n1. Make sure you download NodeJS if you don't already have it here: https://nodejs.org/en/download\n2. After you have updated your claude_desktop_config.json and downloaded NodeJS, make sure you fully\nrestart/exit your Claude Desktop app.\n3. Get started by writing:\n```bash\nCheck to see whether you have access to the Flaunt MCP server.\n```\nThen:\n```bash\nRun the Shopify Overview Dashboard report (or 'Monthly Lifecycle Performance Report' if you integrated Klaviyo) using Flaunt MCP server\n```\n### Connecting claude.ai (OAuth)\nclaude.ai's custom connectors authenticate with OAuth, so there's no key to copy or store.\n**Only Flaunt admins can connect** — the connection uses your existing admin login and is\nlimited to the brands you manage (read-only).\n1. In claude.ai, open **Settings → Connectors → Add custom connector**.\n2. Paste the MCP server URL `https://api.flaunt.xyz/v1/data/mcp` and add it.\n3. Claude opens a Flaunt authorization screen. Sign in with your Flaunt admin login if\n   prompted, review what the assistant can access, and click **Approve**.\n\n![The Flaunt authorization screen shown when connecting an AI assistant](/img/mcp-oauth-consent.png)\n\nThe connection then acts as you, with the same read-only access and brand scoping as your\nadmin account. Nothing changes on the API side — the OAuth token resolves to the same tools\n(`list_brands`, `run_query`, …) and the same daily usage limits as an API key.\n### Managing connected assistants\nSee and revoke your connected assistants any time in your Flaunt admin under **API Keys →\nConnected AI assistants**. Revoking a connection cuts off its access immediately; the\nassistant has to reconnect and be re-approved to regain it. Access is also revoked\nautomatically if you lose admin access to Flaunt."},"servers":[{"url":"https://api.flaunt.xyz","description":"Production"}],"tags":[{"name":"Brands","description":"The brands an API key can access."},{"name":"Reports","description":"Curated, repeatable report definitions and the proven queries that compose them."},{"name":"Queries","description":"List saved queries and run any query (raw SQL or a proven query by id)."},{"name":"Feedback","description":"Report that data looks wrong so the Flaunt data team can investigate."}],"security":[{"bearerAuth":[]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Your Flaunt Data API key, sent as `Authorization: Bearer <key>`."}},"schemas":{"ListBrandsResponse":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"brands":{"type":"array","items":{"type":"object","properties":{"brandId":{"type":"string"},"name":{"anyOf":[{"type":"string"},{"type":"null"}]},"dataset":{"description":"The brand's dataset, or null if not provisioned.","anyOf":[{"type":"string"},{"type":"null"}]}},"required":["brandId","name","dataset"],"additionalProperties":false}}},"required":["brands"],"additionalProperties":false},"Report":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"reportId":{"type":"string"},"brandId":{"description":"The brand this report's queries resolve against.","type":"string"},"name":{"type":"string"},"description":{"anyOf":[{"type":"string"},{"type":"null"}]},"queryCount":{"description":"How many queries (sections) the report contains.","type":"number"},"type":{"description":"Ownership: `system` (global Flaunt report), `org` (shared in your org), or `brand` (your own).","type":"string","enum":["system","org","brand"]}},"required":["reportId","brandId","name","description","queryCount","type"],"additionalProperties":false},"ReportDetail":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"reportId":{"type":"string"},"brandId":{"description":"The brand this report's queries resolve against.","type":"string"},"name":{"type":"string"},"description":{"anyOf":[{"type":"string"},{"type":"null"}]},"queryCount":{"description":"How many queries (sections) the report contains.","type":"number"},"type":{"description":"Ownership: `system` (global Flaunt report), `org` (shared in your org), or `brand` (your own).","type":"string","enum":["system","org","brand"]},"instructions":{"description":"Comprehensive markdown instructions for the report.","anyOf":[{"type":"string"},{"type":"null"}]},"dashboardConfig":{"description":"The report's dashboard layout/tile config (shape varies), or null if it has none.","anyOf":[{},{"type":"null"}]}},"required":["reportId","brandId","name","description","queryCount","type","instructions","dashboardConfig"],"additionalProperties":false},"ListReportsResponse":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"reports":{"type":"array","items":{"type":"object","properties":{"reportId":{"type":"string"},"brandId":{"description":"The brand this report's queries resolve against.","type":"string"},"name":{"type":"string"},"description":{"anyOf":[{"type":"string"},{"type":"null"}]},"queryCount":{"description":"How many queries (sections) the report contains.","type":"number"},"type":{"description":"Ownership: `system` (global Flaunt report), `org` (shared in your org), or `brand` (your own).","type":"string","enum":["system","org","brand"]}},"required":["reportId","brandId","name","description","queryCount","type"],"additionalProperties":false}},"nextCursor":{"description":"Pass as `?cursor=` to fetch the next page. `null` when this is the last page.","anyOf":[{"type":"string"},{"type":"null"}]},"hasMore":{"description":"True when more reports remain beyond this page (i.e. `nextCursor` is set).","type":"boolean"}},"required":["reports","nextCursor","hasMore"],"additionalProperties":false},"ListReportQueriesResponse":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"queries":{"type":"array","items":{"type":"object","properties":{"id":{"description":"Run this via POST /query as `queryId`.","type":"string"},"name":{"anyOf":[{"type":"string"},{"type":"null"}]},"description":{"anyOf":[{"type":"string"},{"type":"null"}]},"sql":{"description":"Parameterized template SQL (with `{slots}`) for report queries; concrete SQL for saved queries.","type":"string"},"tables":{"description":"Source tables when known (saved queries); null for report queries.","anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}]},"parameterized":{"description":"True when the SQL has `{slots}` the server fills in at run time — send a `period` when running it.","type":"boolean"},"parameters":{"description":"Placeholder names the SQL uses (e.g. `start_date`); empty for saved queries.","type":"array","items":{"type":"string"}},"ordinal":{"description":"Section order within its report.","type":"number"},"narrativeHints":{"description":"Narrative guidance for how to write up this section (report sections only).","anyOf":[{"type":"string"},{"type":"null"}]},"chartSpec":{"description":"Spec driving this section's inline chart, when set (report sections only)."}},"required":["id","name","description","sql","tables","parameterized","parameters"],"additionalProperties":false}}},"required":["queries"],"additionalProperties":false},"ListSavedQueriesResponse":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"queries":{"type":"array","items":{"type":"object","properties":{"id":{"description":"Run this via POST /query as `queryId`.","type":"string"},"name":{"anyOf":[{"type":"string"},{"type":"null"}]},"description":{"anyOf":[{"type":"string"},{"type":"null"}]},"sql":{"description":"Parameterized template SQL (with `{slots}`) for report queries; concrete SQL for saved queries.","type":"string"},"tables":{"description":"Source tables when known (saved queries); null for report queries.","anyOf":[{"type":"array","items":{"type":"string"}},{"type":"null"}]},"parameterized":{"description":"True when the SQL has `{slots}` the server fills in at run time — send a `period` when running it.","type":"boolean"},"parameters":{"description":"Placeholder names the SQL uses (e.g. `start_date`); empty for saved queries.","type":"array","items":{"type":"string"}},"ordinal":{"description":"Section order within its report.","type":"number"},"narrativeHints":{"description":"Narrative guidance for how to write up this section (report sections only).","anyOf":[{"type":"string"},{"type":"null"}]},"chartSpec":{"description":"Spec driving this section's inline chart, when set (report sections only)."}},"required":["id","name","description","sql","tables","parameterized","parameters"],"additionalProperties":false}}},"required":["queries"],"additionalProperties":false},"RunQueryRequest":{"$schema":"https://json-schema.org/draft/2020-12/schema","anyOf":[{"type":"object","properties":{"brandId":{"description":"Target brand. Required for organization-scoped keys (unless inferable from a brand-owned queryId); defaults to the key's brand for single-brand keys.","type":"string"},"sql":{"description":"Read-only SQL (SELECT/WITH only, single statement). Reference tables as `flaunt-v1.<dataset>.<table>` using the dataset from GET /brands.","type":"string","minLength":1},"dryRun":{"description":"If true, validate the query and estimate bytes scanned without executing it.","type":"boolean"}},"required":["sql"],"additionalProperties":false},{"type":"object","properties":{"brandId":{"description":"Target brand. Required for organization-scoped keys (unless inferable from a brand-owned queryId); defaults to the key's brand for single-brand keys.","type":"string"},"queryId":{"description":"Run a proven query by id — the `id` from GET /reports/{reportId}/queries or GET /queries.","type":"string","minLength":1},"period":{"description":"Time window for a parameterized query. Omit to default to the last full month.","type":"object","properties":{"cadence":{"description":"Rolling window for a parameterized query: DAILY (yesterday), WEEKLY, or MONTHLY (last full month).","type":"string","enum":["DAILY","WEEKLY","MONTHLY"]},"startDate":{"description":"Explicit period start, ISO `YYYY-MM-DD` (use with endDate).","type":"string"},"endDate":{"description":"Explicit period end, ISO `YYYY-MM-DD` (use with startDate).","type":"string"}}},"dryRun":{"description":"If true, validate the query and estimate bytes scanned without executing it.","type":"boolean"}},"required":["queryId"],"additionalProperties":false}]},"RunQueryResponse":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"brandId":{"description":"The brand the query ran against.","type":"string"},"rows":{"type":"array","items":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"rowCount":{"description":"Number of rows returned in this response (the result page is capped; see `truncated`).","type":"number"},"truncated":{"description":"True if results were capped; add LIMIT/OFFSET to page through the full result set.","type":"boolean"},"schema":{"description":"Result schema (field names and types)."},"stats":{"type":"object","properties":{"dryRun":{"type":"boolean"},"totalBytesBilled":{"description":"Bytes billed for this query (drives cost).","type":"number"},"totalBytesProcessed":{"type":"number"}},"additionalProperties":false}},"required":["brandId","rows","rowCount","truncated"],"additionalProperties":false},"FeedbackRequest":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"summary":{"description":"What looks wrong — the data discrepancy you're reporting.","type":"string","minLength":1},"brandId":{"description":"Which brand's data. Required for org keys unless the report is general.","type":"string"},"expected":{"description":"What you expected the value to be.","type":"string"},"observed":{"description":"The value/metric that looked off.","type":"string"},"severity":{"type":"string","enum":["low","medium","high"]},"transcript":{"description":"The relevant turns of this conversation, verbatim and in order — the user's complaint, the value(s) you showed, and your reasoning. The richest signal for diagnosis; include it whenever possible. If you only have a free-form narrative, put it in a single turn.","type":"array","items":{"type":"object","properties":{"role":{"description":"Who produced this turn.","type":"string","enum":["user","assistant","tool"]},"content":{"description":"The turn's text, verbatim.","type":"string"}},"required":["role","content"]}}},"required":["summary"]},"FeedbackResponse":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"feedbackRef":{"description":"Reference id for the filed report — quote it in support.","type":"string"}},"required":["feedbackRef"],"additionalProperties":false},"ErrorResponse":{"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"requestId":{"type":"string"},"details":{}},"required":["code","message","requestId"],"additionalProperties":false}},"required":["error"],"additionalProperties":false}}},"paths":{"/v1/data/brands":{"get":{"tags":["Brands"],"operationId":"listBrands","summary":"List brands","description":"Returns the brands this API key can access, each with its brandId, name, and dataset.","responses":{"200":{"description":"The brands this key can access.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListBrandsResponse"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/data/reports":{"get":{"tags":["Reports"],"operationId":"listReports","summary":"List reports","description":"Returns the reports (curated, repeatable query definitions) this key can access, as metadata. Includes both your own/organization reports and Flaunt's shared reports, which run against each brand's own data and so appear once per brand. For org keys, omit `brandId` to list across all the org's brands, or pass it to narrow to one. Use `type` to filter by ownership. Results are cursor-paginated: read `nextCursor` from the response and pass it back as `cursor` until it's `null`.","parameters":[{"name":"brandId","in":"query","required":false,"schema":{"type":"string"},"description":"Target brand. Optional for single-brand keys and brand-owned resources; narrows org keys to one brand."},{"name":"type","in":"query","required":false,"schema":{"type":"string","enum":["system","org","brand"]},"description":"Filter to one ownership category: `system` (global Flaunt reports), `org` (shared in your org), or `brand` (your own). Omit for all."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":200,"default":50},"description":"Page size (1–200, default 50)."},{"name":"cursor","in":"query","required":false,"schema":{"type":"string"},"description":"Opaque pagination token from the previous response's `nextCursor`. Omit for the first page."}],"responses":{"200":{"description":"One page of the reports this key can access.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListReportsResponse"}}}},"400":{"description":"Invalid `limit` or `cursor`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/data/reports/{reportId}":{"get":{"tags":["Reports"],"operationId":"getReport","summary":"Get a report","description":"Returns one report's full detail — its metadata plus the markdown `instructions` and the `dashboardConfig` (tile/layout JSON). `brandId` is required only for an org key viewing an org-/all-brands or shared Flaunt report (which has no single owning brand).","parameters":[{"name":"reportId","in":"path","required":true,"schema":{"type":"string"}},{"name":"brandId","in":"query","required":false,"schema":{"type":"string"},"description":"Target brand. Optional for single-brand keys and brand-owned resources; narrows org keys to one brand."}],"responses":{"200":{"description":"The report's detail (metadata + instructions + dashboardConfig).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReportDetail"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No such report is visible to this brand.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/data/reports/{reportId}/queries":{"get":{"tags":["Reports"],"operationId":"listReportQueries","summary":"List a report's queries","description":"Returns the report's queries (sections) in order. Each is runnable via POST /query by its `id` (parameterized queries also accept a `period`).","parameters":[{"name":"reportId","in":"path","required":true,"schema":{"type":"string"}},{"name":"brandId","in":"query","required":false,"schema":{"type":"string"},"description":"Target brand. Optional for single-brand keys and brand-owned resources; narrows org keys to one brand."}],"responses":{"200":{"description":"The report's queries, in section order.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListReportQueriesResponse"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"No such report is visible to this brand.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/data/queries":{"get":{"tags":["Queries"],"operationId":"listSavedQueries","summary":"List saved queries","description":"Returns standalone proven queries a brand has saved (not part of a report). Concrete and runnable by id via POST /query as-is.","parameters":[{"name":"brandId","in":"query","required":false,"schema":{"type":"string"},"description":"Target brand. Optional for single-brand keys and brand-owned resources; narrows org keys to one brand."}],"responses":{"200":{"description":"The saved queries this key can access.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListSavedQueriesResponse"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/data/query":{"post":{"tags":["Queries"],"operationId":"runQuery","summary":"Run a query","description":"Runs a read-only (SELECT/WITH) query against a brand's dataset. Provide **either** raw `sql` **or** a proven `queryId` (from GET /reports/{reportId}/queries or GET /queries). A parameterized report query also accepts a `period` (`cadence`, or `startDate`/`endDate`) — omitted, it defaults to the last full month. For org keys, `brandId` is required unless it's inferable (single-brand key, brand-owned query). Use `dryRun` to estimate bytes scanned before running.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RunQueryRequest"}}}},"responses":{"200":{"description":"Query results (rows may be truncated; see `truncated`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RunQueryResponse"}}}},"400":{"description":"Invalid SQL, query too expensive, or query failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The key can't access the requested brand, or the query referenced a table outside its access.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"The referenced `queryId` was not found or isn't visible to this brand.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"The key reached its daily usage limit (resets on a rolling 24-hour basis).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/data/feedback":{"post":{"tags":["Feedback"],"operationId":"submitFeedback","summary":"Report a data issue","description":"Reports that data looks wrong so the Flaunt data team can investigate. Provide a `summary` of the discrepancy and, ideally, `expected` vs `observed` values, the affected `brandId`, and a `transcript` of the relevant conversation turns. The recent queries this key ran are attached automatically as an audit trail. Returns a `feedbackRef` to quote in support.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FeedbackRequest"}}}},"responses":{"200":{"description":"The report was filed; `feedbackRef` identifies it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FeedbackResponse"}}}},"400":{"description":"Missing `summary` or other invalid input.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Missing or invalid API key.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"The key can't access the requested brand.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}}}