{
  "openapi": "3.1.0",
  "info": {
    "title": "Bellaso Agent API",
    "version": "1.0.0",
    "description": "REST twin of the Bellaso MCP tool registry. Every tool exposed over MCP is also callable here:\nPOST /api/v1/tools/{name} with the tool's arguments as the JSON body.\n\nAuthentication: `Authorization: Bearer ck_...` — an organization-scoped endpoint token minted in the Bellaso console.\nThe token pins the organization; there is no organization parameter on any operation, and any organization id sent in a body is ignored.\nCalls are rate limited per endpoint (per minute and per day) and every call is audited.\n\nSCHEMA CAVEAT: argument schemas are generated from the server's own validation schemas, so they are exact about property names,\ntypes and unknown-key rejection. They cannot express CROSS-FIELD constraints (for example \"start_date must be on or before end_date\"),\nwhich are still enforced server-side and answered with a 400 validation_error. Treat the published schema as necessary, not sufficient."
  },
  "servers": [
    {
      "url": "https://dtrgbxembnbwlmpvhhms.supabase.co/functions/v1/cortex-mcp",
      "description": "Bellaso Agent API"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "discovery",
      "description": "Tool index and this contract"
    },
    {
      "name": "read",
      "description": "Tools that only read data"
    },
    {
      "name": "write",
      "description": "Tools that create or update in-app state"
    },
    {
      "name": "ads",
      "description": "Tools backed by the ads module"
    },
    {
      "name": "channels",
      "description": "Tools backed by the channels module"
    },
    {
      "name": "compass",
      "description": "Tools backed by the compass module"
    },
    {
      "name": "creative",
      "description": "Tools backed by the creative module"
    },
    {
      "name": "email",
      "description": "Tools backed by the email module"
    },
    {
      "name": "finance",
      "description": "Tools backed by the finance module"
    },
    {
      "name": "forecast",
      "description": "Tools backed by the forecast module"
    },
    {
      "name": "ga4",
      "description": "Tools backed by the ga4 module"
    },
    {
      "name": "organic",
      "description": "Tools backed by the organic module"
    },
    {
      "name": "signal_cac",
      "description": "Tools backed by the signal_cac module"
    }
  ],
  "paths": {
    "/api/v1/tools": {
      "get": {
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "tags": [
          "discovery"
        ],
        "operationId": "listTools",
        "summary": "List the tools this token may call",
        "description": "The tools visible to the presenting token: an endpoint with an empty allow-list sees every tool, otherwise only the tools it was granted.",
        "responses": {
          "200": {
            "description": "Tools",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "tools"
                  ],
                  "properties": {
                    "tools": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ToolSummary"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/api/v1/context-pack": {
      "get": {
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "tags": [
          "discovery"
        ],
        "operationId": "getContextPack",
        "summary": "Get the agent context pack",
        "description": "The same result as calling the get_context_pack tool: one markdown document carrying this API's metric semantics, grounding rules, mart catalog and full tool reference. Load it into an agent's context once per session. Runs the same pipeline as any tool call, so it consumes a rate-limit unit and is audited.",
        "responses": {
          "200": {
            "description": "The context pack",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolResult"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/openapi.json": {
      "get": {
        "security": [],
        "tags": [
          "discovery"
        ],
        "operationId": "getOpenApiDocument",
        "summary": "Get this public contract",
        "responses": {
          "200": {
            "description": "OpenAPI 3.1 document"
          }
        }
      }
    },
    "/api/v1/tools/get_data_coverage": {
      "post": {
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "tags": [
          "read"
        ],
        "operationId": "call_get_data_coverage",
        "summary": "Data coverage",
        "description": "Which analytics modules this organization has (entitlements) and which actually have data flowing, with the latest date per surface. CALL THIS FIRST in a new session, and any time you are unsure whether a data surface exists for this org — entitled-but-empty and has-data-but-lapsed are both real states, and every other tool's availability follows from what this returns.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {},
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool ran",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/tools/get_metric_definitions": {
      "post": {
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "tags": [
          "read"
        ],
        "operationId": "call_get_metric_definitions",
        "summary": "Metric definitions",
        "description": "What this organization's custom metric slots (metric_1..metric_10) count, with each metric's name, abbreviation, description, type and display format — plus whether its conversion numbers come from its CRM or from ad-platform pixels. Read this BEFORE interpreting or ranking by any metric_N column: the slots mean different things per organization (for CRM-source orgs metric_1 is typically MQLs from Salesforce/HubSpot, not a platform pixel conversion).",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {},
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool ran",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/tools/get_context_pack": {
      "post": {
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "tags": [
          "read"
        ],
        "operationId": "call_get_context_pack",
        "summary": "Context pack",
        "description": "The full operating contract for this API as one markdown document: how the platform defines its metrics (Monday-start weeks, ROAS as total revenue over total spend, rate metrics recomputed from summed components rather than averaged, the display-format and ad-network vocabularies), the grounding rules your answers must follow, the BigQuery marts behind each tool with their documented columns and grains, and the full tool reference. LOAD THIS ONCE at the start of a session, before answering any question about this organization's numbers — it is what stops a correct-looking figure from being computed the wrong way. It is static per deployment: cache it against the returned meta.version and re-fetch only when that changes.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {},
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool ran",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/tools/get_performance_summary": {
      "post": {
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "tags": [
          "read",
          "ads"
        ],
        "operationId": "call_get_performance_summary",
        "summary": "Ad performance summary",
        "description": "Organization-wide paid-ad totals for one window: spend, impressions, clicks, ctr, cpm, cpc, roas, fatigue_score and this organization's custom metrics (metric_1..metric_10). This is the grounding call for \"how are we doing\" — start here, then drill in with the per-entity tools. Optional filters narrow to one product, network, ad account or campaign. Omit both dates for the default window: the last 30 complete days ending yesterday. Recompute rates (ctr, cpc, cpm, roas) from summed numerators and denominators — ROAS is total revenue ÷ total spend over the window. Never average the per-row rate columns. Read get_metric_definitions before interpreting or comparing any metric_N column: the slots mean different things per organization.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "start_date": {
                    "description": "Window start, YYYY-MM-DD (inclusive). Omit both dates for the default window: the last 30 complete days ending yesterday.",
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "end_date": {
                    "description": "Window end, YYYY-MM-DD (inclusive).",
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "product_id": {
                    "description": "Filter to one product id from the org context.",
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 128
                  },
                  "ad_network_id": {
                    "description": "Filter to one network: facebook_ads | google_ads | tiktok_ads | linkedin_ads | bingads.",
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 64
                  },
                  "account_id": {
                    "description": "Filter to one ad account id.",
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 128
                  },
                  "campaign_id": {
                    "description": "Filter to one campaign id.",
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 128
                  }
                },
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool ran",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/tools/get_daily_performance": {
      "post": {
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "tags": [
          "read",
          "ads"
        ],
        "operationId": "call_get_daily_performance",
        "summary": "Daily ad performance",
        "description": "Per-day paid-ad performance series for a window, filterable to one product, network, ad account, campaign, ad set or ad. Use it for trends, pacing and \"what changed on <date>\". Omit both dates for the default window: the last 30 complete days ending yesterday. Rows are complete days. If you bucket days into weeks, use MONDAY-start weeks — that is the week boundary the Bellaso app and every other report in this product use. Recompute rates (ctr, cpc, cpm, roas) from summed numerators and denominators — ROAS is total revenue ÷ total spend over the window. Never average the per-row rate columns.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "start_date": {
                    "description": "Window start, YYYY-MM-DD (inclusive). Omit both dates for the default window: the last 30 complete days ending yesterday.",
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "end_date": {
                    "description": "Window end, YYYY-MM-DD (inclusive).",
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "product_id": {
                    "description": "Filter to one product id from the org context.",
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 128
                  },
                  "ad_network_id": {
                    "description": "Filter to one network: facebook_ads | google_ads | tiktok_ads | linkedin_ads | bingads.",
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 64
                  },
                  "account_id": {
                    "description": "Filter to one ad account id.",
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 128
                  },
                  "campaign_id": {
                    "description": "Filter to one campaign id.",
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 128
                  },
                  "adset_id": {
                    "description": "Filter to one ad set id.",
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 128
                  },
                  "ad_id": {
                    "description": "Filter to one ad id.",
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 128
                  }
                },
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool ran",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/tools/get_accounts_performance": {
      "post": {
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "tags": [
          "read",
          "ads"
        ],
        "operationId": "call_get_accounts_performance",
        "summary": "Performance by ad account",
        "description": "Paid-ad performance rolled up per ad account, including this organization's custom KPI counts and values. Use it to compare accounts or to find which account carries the spend. For any 'top / most / best by X' question pass sort_by AND limit: the full row set is ranked server-side before the slice, so the answer arrives in one call instead of several narrowing ones. An invalid sort_by comes back with the list of valid columns, so you can retry in one step. Omit both dates for the default window: the last 30 complete days ending yesterday. Recompute rates (ctr, cpc, cpm, roas) from summed numerators and denominators — ROAS is total revenue ÷ total spend over the window. Never average the per-row rate columns.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "start_date": {
                    "description": "Window start, YYYY-MM-DD (inclusive). Omit both dates for the default window: the last 30 complete days ending yesterday.",
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "end_date": {
                    "description": "Window end, YYYY-MM-DD (inclusive).",
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "product_id": {
                    "description": "Filter to one product id from the org context.",
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 128
                  },
                  "ad_network_id": {
                    "description": "Filter to one network: facebook_ads | google_ads | tiktok_ads | linkedin_ads | bingads.",
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 64
                  },
                  "account_id": {
                    "description": "Filter to one ad account id.",
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 128
                  },
                  "campaign_id": {
                    "description": "Filter to one campaign id.",
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 128
                  },
                  "sort_by": {
                    "description": "Sort rows by this column, descending, before returning. Best on numeric metric columns: spend, impressions, clicks, ctr, cpc, cpm, roas, metric_1..metric_10 (for CRM-source orgs metric_1 = MQLs — see get_metric_definitions). An invalid value returns the list of valid columns so you can retry in one step.",
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 64
                  },
                  "limit": {
                    "description": "Max rows to return after sorting (default 50, max 200).",
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 200
                  }
                },
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool ran",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/tools/get_campaigns_performance": {
      "post": {
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "tags": [
          "read",
          "ads"
        ],
        "operationId": "call_get_campaigns_performance",
        "summary": "Performance by campaign",
        "description": "Paid-ad performance rolled up per campaign — \"top campaigns\", \"which campaigns are underperforming\", \"where did the spend go\". Filter to one network or account first when the question is scoped that way. For any 'top / most / best by X' question pass sort_by AND limit: the full row set is ranked server-side before the slice, so the answer arrives in one call instead of several narrowing ones. An invalid sort_by comes back with the list of valid columns, so you can retry in one step. Omit both dates for the default window: the last 30 complete days ending yesterday. Recompute rates (ctr, cpc, cpm, roas) from summed numerators and denominators — ROAS is total revenue ÷ total spend over the window. Never average the per-row rate columns.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "start_date": {
                    "description": "Window start, YYYY-MM-DD (inclusive). Omit both dates for the default window: the last 30 complete days ending yesterday.",
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "end_date": {
                    "description": "Window end, YYYY-MM-DD (inclusive).",
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "product_id": {
                    "description": "Filter to one product id from the org context.",
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 128
                  },
                  "ad_network_id": {
                    "description": "Filter to one network: facebook_ads | google_ads | tiktok_ads | linkedin_ads | bingads.",
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 64
                  },
                  "account_id": {
                    "description": "Filter to one ad account id.",
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 128
                  },
                  "campaign_id": {
                    "description": "Filter to one campaign id.",
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 128
                  },
                  "sort_by": {
                    "description": "Sort rows by this column, descending, before returning. Best on numeric metric columns: spend, impressions, clicks, ctr, cpc, cpm, roas, metric_1..metric_10 (for CRM-source orgs metric_1 = MQLs — see get_metric_definitions). An invalid value returns the list of valid columns so you can retry in one step.",
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 64
                  },
                  "limit": {
                    "description": "Max rows to return after sorting (default 50, max 200).",
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 200
                  }
                },
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool ran",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/tools/get_adsets_performance": {
      "post": {
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "tags": [
          "read",
          "ads"
        ],
        "operationId": "call_get_adsets_performance",
        "summary": "Performance by ad set",
        "description": "Paid-ad performance rolled up per ad set, optionally inside one campaign (campaign_id) or for one ad set (adset_id). Use it after a campaign-level answer, to see which audience or placement inside the campaign is doing the work. For any 'top / most / best by X' question pass sort_by AND limit: the full row set is ranked server-side before the slice, so the answer arrives in one call instead of several narrowing ones. An invalid sort_by comes back with the list of valid columns, so you can retry in one step. Omit both dates for the default window: the last 30 complete days ending yesterday. Recompute rates (ctr, cpc, cpm, roas) from summed numerators and denominators — ROAS is total revenue ÷ total spend over the window. Never average the per-row rate columns.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "start_date": {
                    "description": "Window start, YYYY-MM-DD (inclusive). Omit both dates for the default window: the last 30 complete days ending yesterday.",
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "end_date": {
                    "description": "Window end, YYYY-MM-DD (inclusive).",
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "product_id": {
                    "description": "Filter to one product id from the org context.",
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 128
                  },
                  "ad_network_id": {
                    "description": "Filter to one network: facebook_ads | google_ads | tiktok_ads | linkedin_ads | bingads.",
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 64
                  },
                  "account_id": {
                    "description": "Filter to one ad account id.",
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 128
                  },
                  "campaign_id": {
                    "description": "Filter to one campaign id.",
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 128
                  },
                  "adset_id": {
                    "description": "Filter to one ad set id.",
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 128
                  },
                  "sort_by": {
                    "description": "Sort rows by this column, descending, before returning. Best on numeric metric columns: spend, impressions, clicks, ctr, cpc, cpm, roas, metric_1..metric_10 (for CRM-source orgs metric_1 = MQLs — see get_metric_definitions). An invalid value returns the list of valid columns so you can retry in one step.",
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 64
                  },
                  "limit": {
                    "description": "Max rows to return after sorting (default 50, max 200).",
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 200
                  }
                },
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool ran",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/tools/get_ads_performance": {
      "post": {
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "tags": [
          "read",
          "ads"
        ],
        "operationId": "call_get_ads_performance",
        "summary": "Performance by ad",
        "description": "Paid-ad performance rolled up per individual ad. Ad grain exists for facebook_ads, linkedin_ads and tiktok_ads only — other networks do not report it, so an empty result for google_ads or bingads is expected rather than a data problem. For CRM-source organizations these rows are CRM-attributed at ad grain and match the platform's Ads page, which makes this THE authoritative source for per-ad and per-creative MQL ranking: rank by metric_1 (see get_metric_definitions). Never take that ranking from get_creative_fatigue or get_creative_leaderboard — their conversion columns are platform-pixel and near zero by design for those organizations. For any 'top / most / best by X' question pass sort_by AND limit: the full row set is ranked server-side before the slice, so the answer arrives in one call instead of several narrowing ones. An invalid sort_by comes back with the list of valid columns, so you can retry in one step. Omit both dates for the default window: the last 30 complete days ending yesterday.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "start_date": {
                    "description": "Window start, YYYY-MM-DD (inclusive). Omit both dates for the default window: the last 30 complete days ending yesterday.",
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "end_date": {
                    "description": "Window end, YYYY-MM-DD (inclusive).",
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "product_id": {
                    "description": "Filter to one product id from the org context.",
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 128
                  },
                  "ad_network_id": {
                    "description": "Filter to one network: facebook_ads | google_ads | tiktok_ads | linkedin_ads | bingads.",
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 64
                  },
                  "account_id": {
                    "description": "Filter to one ad account id.",
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 128
                  },
                  "campaign_id": {
                    "description": "Filter to one campaign id.",
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 128
                  },
                  "adset_id": {
                    "description": "Filter to one ad set id.",
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 128
                  },
                  "sort_by": {
                    "description": "Sort rows by this column, descending, before returning. Best on numeric metric columns: spend, impressions, clicks, ctr, cpc, cpm, roas, metric_1..metric_10 (for CRM-source orgs metric_1 = MQLs — see get_metric_definitions). An invalid value returns the list of valid columns so you can retry in one step.",
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 64
                  },
                  "limit": {
                    "description": "Max rows to return after sorting (default 50, max 200).",
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 200
                  }
                },
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool ran",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/tools/get_keywords_performance": {
      "post": {
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "tags": [
          "read",
          "ads"
        ],
        "operationId": "call_get_keywords_performance",
        "summary": "Performance by keyword",
        "description": "Keyword-level performance with match_type, for google_ads and bingads only (the social networks have no keyword grain, so filtering this to one of them returns nothing by design). Filter with campaign_id / adset_id to scope to one ad group, or keyword_id for a single keyword. For any 'top / most / best by X' question pass sort_by AND limit: the full row set is ranked server-side before the slice, so the answer arrives in one call instead of several narrowing ones. An invalid sort_by comes back with the list of valid columns, so you can retry in one step. Omit both dates for the default window: the last 30 complete days ending yesterday.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "start_date": {
                    "description": "Window start, YYYY-MM-DD (inclusive). Omit both dates for the default window: the last 30 complete days ending yesterday.",
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "end_date": {
                    "description": "Window end, YYYY-MM-DD (inclusive).",
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "product_id": {
                    "description": "Filter to one product id from the org context.",
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 128
                  },
                  "ad_network_id": {
                    "description": "Filter to one network: facebook_ads | google_ads | tiktok_ads | linkedin_ads | bingads.",
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 64
                  },
                  "account_id": {
                    "description": "Filter to one ad account id.",
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 128
                  },
                  "campaign_id": {
                    "description": "Filter to one campaign id.",
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 128
                  },
                  "adset_id": {
                    "description": "Filter to one ad set id.",
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 128
                  },
                  "keyword_id": {
                    "description": "Filter to one keyword id.",
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 128
                  },
                  "sort_by": {
                    "description": "Sort rows by this column, descending, before returning. Best on numeric metric columns: spend, impressions, clicks, ctr, cpc, cpm, roas, metric_1..metric_10 (for CRM-source orgs metric_1 = MQLs — see get_metric_definitions). An invalid value returns the list of valid columns so you can retry in one step.",
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 64
                  },
                  "limit": {
                    "description": "Max rows to return after sorting (default 50, max 200).",
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 200
                  }
                },
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool ran",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/tools/get_geo_performance": {
      "post": {
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "tags": [
          "read",
          "ads"
        ],
        "operationId": "call_get_geo_performance",
        "summary": "Performance by geography",
        "description": "Country-level spend, clicks and conversions, rolled up either by ad account (level=\"account\", the default) or by campaign (level=\"campaign\"). country_code narrows to a single country. Coverage depends on what each network reports geographically, so a country missing from the rows means \"not reported\", not \"zero\". Omit both dates for the default window: the last 30 complete days ending yesterday.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "start_date": {
                    "description": "Window start, YYYY-MM-DD (inclusive). Omit both dates for the default window: the last 30 complete days ending yesterday.",
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "end_date": {
                    "description": "Window end, YYYY-MM-DD (inclusive).",
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "product_id": {
                    "description": "Filter to one product id from the org context.",
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 128
                  },
                  "ad_network_id": {
                    "description": "Filter to one network: facebook_ads | google_ads | tiktok_ads | linkedin_ads | bingads.",
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 64
                  },
                  "account_id": {
                    "description": "Filter to one ad account id.",
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 128
                  },
                  "campaign_id": {
                    "description": "Filter to one campaign id.",
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 128
                  },
                  "level": {
                    "description": "Rollup grain: 'account' (default) or 'campaign'.",
                    "type": "string",
                    "enum": [
                      "account",
                      "campaign"
                    ]
                  },
                  "country_code": {
                    "description": "Filter to one country, as its 2- or 3-letter code (e.g. US).",
                    "type": "string",
                    "minLength": 2,
                    "maxLength": 3,
                    "pattern": "^[A-Za-z]{2,3}$"
                  }
                },
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool ran",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/tools/get_creative_fatigue": {
      "post": {
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "tags": [
          "read",
          "ads"
        ],
        "operationId": "call_get_creative_fatigue",
        "summary": "Fatigued ads",
        "description": "Ads whose creative fatigue score is elevated, highest first — the tool for \"which ads are burning out / need a refresh\". The score adds a CTR-decay component (up to 50), an impression-pressure component (up to 30) and a creative-age component (up to 20), and an ad younger than 7 days always scores 0. min_score defaults to 30; only ads with spend in the window are considered, and at most 200 rows come back. This reads engagement and delivery metrics plus PLATFORM-PIXEL conversions: for CRM-source organizations the conversion columns here are near zero by design, so never rank creatives by MQL or lead volume with this tool — use get_ads_performance. Omit both dates for the default window: the last 30 complete days ending yesterday.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "start_date": {
                    "description": "Window start, YYYY-MM-DD (inclusive). Omit both dates for the default window: the last 30 complete days ending yesterday.",
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "end_date": {
                    "description": "Window end, YYYY-MM-DD (inclusive).",
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "ad_network_id": {
                    "description": "Filter to one network: facebook_ads | google_ads | tiktok_ads | linkedin_ads | bingads.",
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 64
                  },
                  "min_score": {
                    "description": "Minimum fatigue score to include, 0-100 (default 30).",
                    "type": "number",
                    "minimum": 0,
                    "maximum": 100
                  }
                },
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool ran",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/tools/get_creative_leaderboard": {
      "post": {
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "tags": [
          "read",
          "creative"
        ],
        "operationId": "call_get_creative_leaderboard",
        "summary": "Creative leaderboard",
        "description": "Per-creative leaderboard with winner / mid / loser labels, trend, spend, CTR, KPI count, cost per KPI and the extracted creative attributes (visual style, messaging angle, density, specificity, human presence, subject elements) — the tool for \"what is working creatively\" and \"what do our winners have in common\". window_days accepts only 30, 60, 90 or 120 (attributes are precomputed nightly for those windows); it takes no start_date/end_date. Its metrics are ad-platform reported, so for CRM-source organizations the KPI columns are near zero by design — rank creatives by MQL with get_ads_performance instead. Requires the Creative module: without it this returns a clean \"not part of this organization's plan\" refusal rather than empty data.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "window_days": {
                    "description": "Lookback window in days. Only 30, 60, 90 and 120 exist (the attributes are precomputed nightly for those windows); default 90.",
                    "type": "number",
                    "enum": [
                      30,
                      60,
                      90,
                      120
                    ]
                  },
                  "sort": {
                    "description": "Ranking column (default cost_per_kpi). cost_per_kpi orders cheapest-first (best first); spend, ctr and kpis order highest-first.",
                    "type": "string",
                    "enum": [
                      "cost_per_kpi",
                      "spend",
                      "ctr",
                      "kpis"
                    ]
                  },
                  "limit": {
                    "description": "Rows to return (default 25, max 100).",
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 100
                  }
                },
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool ran",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/tools/get_channels": {
      "post": {
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "tags": [
          "read",
          "channels"
        ],
        "operationId": "call_get_channels",
        "summary": "All channels",
        "description": "Cross-channel performance from the unified channel_daily mart, which unions paid ads, email, organic social, GA4 web sessions, ecommerce orders and Signal conversions onto ONE canonical channel grain: Paid Search, Paid Social, Paid Other, Organic Search, Organic Social, Email, Direct, Referral, Affiliate, Other. This is the tool for channel mix, blended CAC/ROAS and any \"which channel\" question — the per-platform tools cannot be added up into these numbers.\nmode=overview returns per-channel spend, impressions, clicks, sends, delivered, opens, reach, views, engagements, followers, sessions and key_events, plus a picked conversions and revenue with cac and roas, the native reach/engagement metric with its label, a per-platform breakdown and prior-period deltas, with a totals row spanning all channels. mode=daily returns the per-day, per-channel spend / sessions / conversions / revenue series.\nconversions is picked orders → Signal → GA4 key events and revenue order_revenue → signal_revenue → ga4_revenue, applied consistently across channels, so the comparison is like for like — say which basis you are quoting. There are no filters beyond the window. Omit both dates for the default window: the last 30 complete days ending yesterday. If you bucket the daily series into weeks, use MONDAY-start weeks, the boundary the rest of this product uses.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "start_date": {
                    "description": "Window start, YYYY-MM-DD (inclusive). Omit both dates for the default window: the last 30 complete days ending yesterday.",
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "end_date": {
                    "description": "Window end, YYYY-MM-DD (inclusive).",
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "mode": {
                    "type": "string",
                    "enum": [
                      "overview",
                      "daily"
                    ],
                    "description": "overview = per-channel totals with deltas and a totals row; daily = the per-day, per-channel series."
                  }
                },
                "required": [
                  "mode"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool ran",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/tools/get_ga4": {
      "post": {
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "tags": [
          "read",
          "ga4"
        ],
        "operationId": "call_get_ga4",
        "summary": "GA4 web analytics",
        "description": "Google Analytics 4 web analytics for this organization. Pick the mode for the question: summary (sessions / users / conversions totals), daily (per-day series), traffic_sources (by source and medium), campaigns (by GA4 campaign), channels (GA4's default channel grouping), engagement (sessions, bounce and duration by channel), key_events (GA4 conversion events), landing_pages, or geo_devices (device category and geography).\nThese are GA4's own session-scoped, GA4-attributed numbers: they will NOT tie out to ad-platform clicks or to the paid-ads tools, and GA4 channel groupings are not the Bellaso channel groupings — use get_channels for cross-channel comparison and say which source you are quoting. property_id limits to one property when several are connected. Omit both dates for the default window: the last 30 complete days ending yesterday. Weeks are MONDAY-start if you bucket the daily series.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "start_date": {
                    "description": "Window start, YYYY-MM-DD (inclusive). Omit both dates for the default window: the last 30 complete days ending yesterday.",
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "end_date": {
                    "description": "Window end, YYYY-MM-DD (inclusive).",
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "mode": {
                    "type": "string",
                    "enum": [
                      "summary",
                      "daily",
                      "traffic_sources",
                      "campaigns",
                      "channels",
                      "engagement",
                      "key_events",
                      "landing_pages",
                      "geo_devices"
                    ],
                    "description": "Which GA4 cut to return — see the tool description for each."
                  },
                  "property_id": {
                    "description": "Limit to one GA4 property id. Omit (or pass \"all\") to cover every connected property.",
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 64,
                    "pattern": "^[A-Za-z0-9_-]+$"
                  }
                },
                "required": [
                  "mode"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool ran",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/tools/get_email": {
      "post": {
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "tags": [
          "read",
          "email"
        ],
        "operationId": "call_get_email",
        "summary": "Email marketing",
        "description": "Email marketing analytics from the email_campaigns mart. Pick the mode: summary (sends, delivered, opens, clicks, unsubscribes, bounces with prior-period deltas), daily (per-day series), campaigns (the per-campaign table — sortable with sort_by/sort_dir, searchable with search over subject and campaign name, paged with limit/offset), subject_lines (subject leaderboard plus open/click rates broken out by subject feature, length bucket, weekday, hour and split test; min_sends gates who reaches the leaderboard), or filter_options (all-time campaign types, lists, sources and the available date range — call this first when you need a valid filter value).\nFilters: campaign_type (exact), list_name (substring), source (exact). Open rates are inflated by privacy proxies on some clients — treat click-through and CTOR as the reliable engagement signals, and never present open rate as a deliverability measure. Omit both dates for the default window: the last 30 complete days ending yesterday.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "start_date": {
                    "description": "Window start, YYYY-MM-DD (inclusive). Omit both dates for the default window: the last 30 complete days ending yesterday.",
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "end_date": {
                    "description": "Window end, YYYY-MM-DD (inclusive).",
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "mode": {
                    "type": "string",
                    "enum": [
                      "summary",
                      "daily",
                      "campaigns",
                      "subject_lines",
                      "filter_options"
                    ],
                    "description": "Which email cut to return — see the tool description for each."
                  },
                  "campaign_type": {
                    "description": "Filter to one campaign type (exact match; get the available values from mode=filter_options).",
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200,
                    "pattern": "^[^'\\\\]+$"
                  },
                  "list_name": {
                    "description": "Filter to campaigns sent to a list whose name CONTAINS this text (substring match).",
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200,
                    "pattern": "^[^'\\\\]+$"
                  },
                  "source": {
                    "description": "Filter to one email source/connector (exact match).",
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200,
                    "pattern": "^[^'\\\\]+$"
                  },
                  "sort_by": {
                    "description": "campaigns mode: sort column — sent_at | sends | delivered | unique_opens | unique_clicks | open_rate | click_rate | ctor | unsub_rate | bounce_rate | campaign_name | subject (default sent_at). An invalid value returns the allowed list.",
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 64
                  },
                  "sort_dir": {
                    "description": "Sort direction for the table mode (default desc).",
                    "type": "string",
                    "enum": [
                      "asc",
                      "desc"
                    ]
                  },
                  "limit": {
                    "description": "Table mode: rows to return, 1-500 (default 25).",
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 500
                  },
                  "offset": {
                    "description": "Table mode: rows to skip, for paging (default 0).",
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 100000
                  },
                  "search": {
                    "description": "campaigns mode: case-insensitive match on the subject or campaign name.",
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "min_sends": {
                    "description": "subject_lines mode: minimum total sends before a subject enters the leaderboard (default 100; 0 removes the gate). Lower it for a low-volume sender.",
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 100000
                  }
                },
                "required": [
                  "mode"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool ran",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/tools/get_organic": {
      "post": {
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "tags": [
          "read",
          "organic"
        ],
        "operationId": "call_get_organic",
        "summary": "Organic social",
        "description": "Organic (unpaid) social analytics from the organic_social_daily and organic_social_posts marts — Facebook Pages and Instagram Business accounts today. Pick the mode: summary (followers, follower_change, reach, views, engagements, likes/comments/shares/saves, link_clicks, posts_published and engagement_rate, with prior-period deltas), daily (per-day series), accounts (per-account rollup with the latest follower count, ordered by reach), posts (the per-post table — sortable with sort_by/sort_dir, searchable with search over the caption, filterable by media_type, paged with limit/offset), or filter_options (all-time platforms, accounts, media types and the available date range).\nfollowers is the LATEST per-account count inside the window, never a sum — do not add it across days or accounts. These are unpaid metrics only: paid social lives in the ads tools, and get_channels is what compares the two. Filters: platform (facebook | instagram), account_id (exact). Omit both dates for the default window: the last 30 complete days ending yesterday.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "start_date": {
                    "description": "Window start, YYYY-MM-DD (inclusive). Omit both dates for the default window: the last 30 complete days ending yesterday.",
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "end_date": {
                    "description": "Window end, YYYY-MM-DD (inclusive).",
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "mode": {
                    "type": "string",
                    "enum": [
                      "summary",
                      "daily",
                      "accounts",
                      "posts",
                      "filter_options"
                    ],
                    "description": "Which organic-social cut to return — see the tool description for each."
                  },
                  "platform": {
                    "description": "Filter to one platform.",
                    "type": "string",
                    "enum": [
                      "facebook",
                      "instagram"
                    ]
                  },
                  "account_id": {
                    "description": "Filter to one social account id (exact; get ids from mode=accounts or mode=filter_options).",
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200,
                    "pattern": "^[^'\\\\]+$"
                  },
                  "sort_by": {
                    "description": "posts mode: sort column — published_at | reach | views | likes | comments | shares | saves | engagements | engagement_rate (default published_at). An invalid value returns the allowed list.",
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 64
                  },
                  "sort_dir": {
                    "description": "Sort direction for the table mode (default desc).",
                    "type": "string",
                    "enum": [
                      "asc",
                      "desc"
                    ]
                  },
                  "limit": {
                    "description": "Table mode: rows to return, 1-500 (default 25).",
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 500
                  },
                  "offset": {
                    "description": "Table mode: rows to skip, for paging (default 0).",
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 100000
                  },
                  "search": {
                    "description": "posts mode: case-insensitive match on the post caption.",
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "media_type": {
                    "description": "posts mode: filter to one media type (e.g. IMAGE, VIDEO, CAROUSEL_ALBUM, REEL, STORY).",
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200,
                    "pattern": "^[^'\\\\]+$"
                  }
                },
                "required": [
                  "mode"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool ran",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/tools/get_forecast": {
      "post": {
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "tags": [
          "read",
          "forecast"
        ],
        "operationId": "call_get_forecast",
        "summary": "Performance forecast",
        "description": "Projected daily performance from the forecast mart (BigQuery AI.FORECAST fitted over recent actuals), organization-wide or filtered to one product, network, account or campaign. mode=summary (default) returns projected totals for the window; mode=daily returns the projected per-day series.\nThese are MODEL PROJECTIONS, not observed data: label them as forecasts, never add them into an actuals total, and do not present a projected value as something that happened. The mart holds a short FORWARD horizon (about a week ahead, rebuilt nightly), and the window simply selects which of those days come back — a window entirely in the past returns nothing, which is expected rather than a failure. Omit both dates for the default window: the last 30 complete days ending yesterday.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "start_date": {
                    "description": "Window start, YYYY-MM-DD (inclusive). Omit both dates for the default window: the last 30 complete days ending yesterday.",
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "end_date": {
                    "description": "Window end, YYYY-MM-DD (inclusive).",
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "product_id": {
                    "description": "Filter to one product id from the org context.",
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 128
                  },
                  "ad_network_id": {
                    "description": "Filter to one network: facebook_ads | google_ads | tiktok_ads | linkedin_ads | bingads.",
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 64
                  },
                  "account_id": {
                    "description": "Filter to one ad account id.",
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 128
                  },
                  "campaign_id": {
                    "description": "Filter to one campaign id.",
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 128
                  },
                  "mode": {
                    "description": "summary (default) = projected totals for the window; daily = the projected per-day series.",
                    "type": "string",
                    "enum": [
                      "summary",
                      "daily"
                    ]
                  }
                },
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool ran",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/tools/get_signal_cac": {
      "post": {
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "tags": [
          "read",
          "signal_cac"
        ],
        "operationId": "call_get_signal_cac",
        "summary": "Signal CAC by channel",
        "description": "Cost per attributed conversion from Signal's spend-attribution mart, by date, attribution model, channel and campaign: spend, credited_conversions and CAC. Use it for \"what does a conversion cost by channel\" once Signal is tracking conversions.\nRows exist ONLY where credited conversions join ad spend on matching campaign names, so an empty result is a valid state — it means nothing has joined yet, not that CAC is zero and not that the tool failed. Because credit is split by attribution model, always state which model a CAC figure comes from. Requires the Signal spend/CAC mart: without the entitlement this returns a plan refusal rather than empty rows.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "start_date": {
                    "description": "Since date, YYYY-MM-DD (default: 90 days ago). The window always runs from here through the latest available day; there is no end_date.",
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  }
                },
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool ran",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/tools/get_finance": {
      "post": {
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "tags": [
          "read",
          "finance"
        ],
        "operationId": "call_get_finance",
        "summary": "Financials (books)",
        "description": "Financials read straight from this organization's books (QuickBooks or Xero). Pick the report: kpis (monthly revenue, COGS and margins), pl (P&L rows by account), balances (balance-sheet lines including cash), ar_aging (receivables by age bucket), ap_aging (payables by age bucket), or marketing_bridge (ad spend against revenue: MER, blended CAC, spend as a percentage of revenue).\nsince sets the earliest month, default 13 months back. These are ACCOUNTING numbers on the organization's own calendar and recognition rules — they will not match ad-platform revenue columns, and marketing_bridge is the intended place to compare the two. Requires the Margin module and a synced accounting connector; without it you get a plan refusal, and with no synced books an explicit \"no accounting data\" answer.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "report": {
                    "type": "string",
                    "enum": [
                      "kpis",
                      "pl",
                      "balances",
                      "ar_aging",
                      "ap_aging",
                      "marketing_bridge"
                    ],
                    "description": "Which financial report to return — see the tool description for each."
                  },
                  "since": {
                    "description": "Earliest month to include, YYYY-MM-DD (default: 13 months back).",
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  }
                },
                "required": [
                  "report"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool ran",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/tools/get_mmm_results": {
      "post": {
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "tags": [
          "read",
          "compass"
        ],
        "operationId": "call_get_mmm_results",
        "summary": "Marketing mix model results",
        "description": "The latest COMPLETED marketing-mix-model run per outcome KPI and model type (Meridian and/or Robyn): model fit (R², NRMSE), per-channel contribution share, ROI and marginal ROI, plus any stored budget-optimization output. outcome_variable filters to one KPI.\nThese are MODEL ESTIMATES over a training window, not measured attribution: report them as modeled contribution, quote the fit statistics alongside any ROI you cite, and note the run's completed_at — a stale run describes an old media mix. Contribution shares answer \"what is driving outcomes\", while get_channels answers \"what did each channel actually spend and produce\". Requires Compass.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "outcome_variable": {
                    "description": "Filter to one outcome variable (the KPI the model was trained to explain). Omit to get every outcome the org has trained.",
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 128
                  }
                },
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool ran",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/tools/run_report": {
      "post": {
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "tags": [
          "read"
        ],
        "operationId": "call_run_report",
        "summary": "Run a report template",
        "description": "Run a versioned, deterministic report template. The same template over the same window always produces identical tables — the fetches are fixed and the math is precomputed — so prefer this over assembling the same story by hand from several tools.\nTemplates: performance_recap (this period against the previous one, top campaigns, biggest movers), budget_pacing (expected vs actual spend for the current budget period), signal_attribution (credit by channel and model plus CAC), creative_review (winners and losers with attribute insights), finance_snapshot (P&L KPIs plus the marketing bridge). Each template reads the same gated tools you would call yourself: a multi-source template (performance_recap, creative_review, finance_snapshot) fills the sections it can and leaves the rest empty, while a single-source one (budget_pacing) comes back as a plain failure carrying the reason — for example \"no budget period covers this date\".\nThe payload is designed to be rendered as tables: summarize the headline movements and name the window, do not restate every row. Omit both dates for the default window: the last 30 complete days ending yesterday.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "template": {
                    "type": "string",
                    "enum": [
                      "performance_recap",
                      "budget_pacing",
                      "signal_attribution",
                      "creative_review",
                      "finance_snapshot"
                    ],
                    "description": "Which report template to run."
                  },
                  "start_date": {
                    "description": "Window start, YYYY-MM-DD (inclusive). Omit both dates for the default window: the last 30 complete days ending yesterday.",
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "end_date": {
                    "description": "Window end, YYYY-MM-DD (inclusive).",
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  }
                },
                "required": [
                  "template"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool ran",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/tools/list_management_connections": {
      "post": {
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "tags": [
          "read"
        ],
        "operationId": "call_list_management_connections",
        "summary": "List management connections",
        "description": "The ad-platform connections this organization can apply changes through, as { id, platform, name }. Call this BEFORE changes_draft: a draft must name one of these ids, and any other id is refused. Returns identifiers only — never tokens, credentials or account contents.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {},
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool ran",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/tools/changes_draft": {
      "post": {
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "tags": [
          "write"
        ],
        "operationId": "call_changes_draft",
        "summary": "Draft a change set",
        "description": "Open a DRAFT change set for this organization — the container a human later reviews and approves. Nothing is applied to any ad platform by this call, or by anything else you can do: after changes_add_item and changes_submit the set sits in PENDING review until a person approves it in the Bellaso app. The draft is owned by this API endpoint, so only this endpoint can add items to it or submit it.",
        "x-openai-isConsequential": true,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "title": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200,
                    "description": "Short human-readable title for the change set — the reviewer sees this first. Say what changes and why, e.g. \"Cut spend on non-converting search campaigns\"."
                  },
                  "management_connection_id": {
                    "type": "string",
                    "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
                    "description": "Which ad-platform connection these changes will be applied through. Get valid ids from list_management_connections."
                  },
                  "requires_finance": {
                    "description": "Route the set through finance approval before admin approval. Use it for budget increases.",
                    "type": "boolean"
                  }
                },
                "required": [
                  "title",
                  "management_connection_id"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool ran",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/tools/changes_add_item": {
      "post": {
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "tags": [
          "write"
        ],
        "operationId": "call_changes_add_item",
        "summary": "Add an item to a change set",
        "description": "Append one proposed operation (a budget change, a status change, …) to a DRAFT change set you drafted. Items can only be added while the set is a DRAFT — once submitted, its contents are frozen so a reviewer approves exactly what they read. Re-adding an identical operation is refused as a duplicate rather than queued twice.",
        "x-openai-isConsequential": true,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "change_set_id": {
                    "type": "string",
                    "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
                    "description": "The DRAFT change set to append to (from changes_draft)."
                  },
                  "operation_type": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120,
                    "description": "The operation this item performs, e.g. \"google.campaign_budget_micros\" or \"meta.adset_daily_budget\"."
                  },
                  "payload": {
                    "type": "object",
                    "propertyNames": {
                      "type": "string"
                    },
                    "additionalProperties": {},
                    "description": "The operation's arguments, matching the operation type — e.g. { customerId, campaignId, budgetMicros } for google.campaign_budget_micros."
                  },
                  "schema_version": {
                    "description": "Payload schema version (defaults to 1). Leave unset unless you know the operation has a newer shape.",
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 100
                  }
                },
                "required": [
                  "change_set_id",
                  "operation_type",
                  "payload"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool ran",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/tools/changes_submit": {
      "post": {
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "tags": [
          "write"
        ],
        "operationId": "call_changes_submit",
        "summary": "Submit a change set for review",
        "description": "Hand a DRAFT change set you drafted to its human reviewers: it moves to PENDING_ADMIN, or PENDING_FINANCE when the set was flagged as requiring finance. This does NOT apply anything — approval is a person's decision in the Bellaso app, and this surface cannot approve. A set that is not a DRAFT is refused.",
        "x-openai-isConsequential": true,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "change_set_id": {
                    "type": "string",
                    "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
                    "description": "The DRAFT change set to submit for human review."
                  }
                },
                "required": [
                  "change_set_id"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool ran",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/tools/list_change_sets": {
      "post": {
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "tags": [
          "read"
        ],
        "operationId": "call_list_change_sets",
        "summary": "List change sets",
        "description": "This organization's change sets, newest first, with their workflow status — including ones drafted by people in the app, not just by this endpoint. Use it to check whether a set you submitted has been approved, rejected or applied. Optionally filter by status.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "status": {
                    "description": "Only return change sets in this workflow state.",
                    "type": "string",
                    "enum": [
                      "DRAFT",
                      "PENDING_FINANCE",
                      "PENDING_ADMIN",
                      "APPROVED",
                      "APPLYING",
                      "APPLIED",
                      "PARTIAL_FAILED",
                      "FAILED",
                      "REJECTED",
                      "CANCELLED"
                    ]
                  },
                  "limit": {
                    "description": "Max change sets to return, newest first (default 50, max 200).",
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 200
                  }
                },
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool ran",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/tools/get_change_set": {
      "post": {
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "tags": [
          "read"
        ],
        "operationId": "call_get_change_set",
        "summary": "Get a change set",
        "description": "One change set with its proposed items and its approval history (who approved or rejected it, and any comment). Use it after list_change_sets to read exactly what a set contains, or to see why a reviewer sent it back.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "change_set_id": {
                    "type": "string",
                    "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
                    "description": "The change set to read."
                  }
                },
                "required": [
                  "change_set_id"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool ran",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/tools/list_audiences": {
      "post": {
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "tags": [
          "read"
        ],
        "operationId": "call_list_audiences",
        "summary": "List audiences",
        "description": "This organization's saved intent audiences, newest first, with the query each was built from, its filters, its status and its ContactBased feed id. Call this before creating one: audiences are capped per organization, and an existing audience can usually be refined with audience_update_filters instead.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {},
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool ran",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/tools/audience_create": {
      "post": {
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "tags": [
          "write"
        ],
        "operationId": "call_audience_create",
        "summary": "Create an audience",
        "description": "Build and save an intent audience from a plain-English description. SIDE EFFECT: this creates a feed at the ContactBased intent provider and CONSUMES this organization's Engage audience quota — it is not a free preview, so confirm the query with the user before calling it. There is a per-organization cap on saved audiences; when it is reached the call is refused and no quota is spent. The audience is created unattached: nothing is pushed to any ad platform until a destination is configured and a person runs a push.",
        "x-openai-isConsequential": true,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200,
                    "description": "Name for the saved audience, shown in the Bellaso app."
                  },
                  "nl_query": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 500,
                    "pattern": "\\S",
                    "description": "Plain-English description of the people to reach, e.g. \"homeowners shopping for solar panels\". This is the intent query the audience is built from."
                  },
                  "lookback_days": {
                    "description": "How many days of intent signal to draw on (1–14, default 14). Values outside the range are clamped.",
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 14
                  },
                  "similarity_preset": {
                    "description": "Match precision: high = fewer, closer matches; low = broader reach. Default medium.",
                    "type": "string",
                    "enum": [
                      "low",
                      "medium",
                      "high"
                    ]
                  },
                  "output_type": {
                    "description": "Whether the audience is people (consumer, the default) or companies/professionals (business).",
                    "type": "string",
                    "enum": [
                      "consumer",
                      "business"
                    ]
                  },
                  "business_filters": {
                    "description": "Firmographic/professional filters, e.g. [{ field: \"job_title\", operator: \"contains\", value: \"engineer\" }]. Unknown fields or operators are dropped silently rather than rejected — read the saved audience back to see what was kept.",
                    "maxItems": 20,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "field": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 64
                        },
                        "operator": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 32
                        },
                        "value": {}
                      },
                      "required": [
                        "field",
                        "operator",
                        "value"
                      ],
                      "additionalProperties": false
                    }
                  },
                  "consumer_filters": {
                    "description": "Consumer filters, e.g. [{ field: \"state\", operator: \"in\", value: [\"CA\",\"NV\"] }]. Unknown fields or operators are dropped silently.",
                    "maxItems": 20,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "field": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 64
                        },
                        "operator": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 32
                        },
                        "value": {}
                      },
                      "required": [
                        "field",
                        "operator",
                        "value"
                      ],
                      "additionalProperties": false
                    }
                  }
                },
                "required": [
                  "name",
                  "nl_query"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool ran",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/tools/audience_update_filters": {
      "post": {
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "tags": [
          "write"
        ],
        "operationId": "call_audience_update_filters",
        "summary": "Update audience filters",
        "description": "Replace a saved audience's structured filters WITHOUT rebuilding it — no ContactBased feed is created and no quota is spent. Only the filter sets you supply are touched; an omitted set is left as it was. Unknown filter fields are dropped by the server, so supplying only unrecognised clauses CLEARS that filter set — read the returned audience to confirm what was kept.",
        "x-openai-isConsequential": true,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "audience_id": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 64,
                    "description": "The audience to edit."
                  },
                  "business_filters": {
                    "description": "Firmographic/professional filters, e.g. [{ field: \"job_title\", operator: \"contains\", value: \"engineer\" }]. Unknown fields or operators are dropped silently rather than rejected — read the saved audience back to see what was kept.",
                    "maxItems": 20,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "field": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 64
                        },
                        "operator": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 32
                        },
                        "value": {}
                      },
                      "required": [
                        "field",
                        "operator",
                        "value"
                      ],
                      "additionalProperties": false
                    }
                  },
                  "consumer_filters": {
                    "description": "Consumer filters, e.g. [{ field: \"state\", operator: \"in\", value: [\"CA\",\"NV\"] }]. Unknown fields or operators are dropped silently.",
                    "maxItems": 20,
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "field": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 64
                        },
                        "operator": {
                          "type": "string",
                          "minLength": 1,
                          "maxLength": 32
                        },
                        "value": {}
                      },
                      "required": [
                        "field",
                        "operator",
                        "value"
                      ],
                      "additionalProperties": false
                    }
                  }
                },
                "required": [
                  "audience_id"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool ran",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/tools/audience_add_destination": {
      "post": {
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "tags": [
          "write"
        ],
        "operationId": "call_audience_add_destination",
        "summary": "Add an audience destination",
        "description": "Configure where an audience will be pushed: one ad account on meta, google or linkedin. This only SAVES the destination in a pending state — it does not push anything, and this API cannot push. The ad account must be one this organization has already synced; any other id is refused by name.",
        "x-openai-isConsequential": true,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "audience_id": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 64,
                    "description": "The audience to configure a destination for."
                  },
                  "platform": {
                    "type": "string",
                    "enum": [
                      "meta",
                      "google",
                      "linkedin"
                    ],
                    "description": "Which ad platform the audience will be pushed to."
                  },
                  "external_account_id": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 64,
                    "description": "The ad account id on that platform, as it appears in this organization's synced accounts (Meta numeric id, Google customer id digits, LinkedIn id). An account this organization has not synced is refused."
                  }
                },
                "required": [
                  "audience_id",
                  "platform",
                  "external_account_id"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool ran",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/tools/account_update": {
      "post": {
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "tags": [
          "write"
        ],
        "operationId": "call_account_update",
        "summary": "Rename or hide an ad account",
        "description": "Set an ad account's display name and/or hide it from the app's pickers and tables. These are the only two account fields this API can touch — both are cosmetic. Product and KPI MAPPINGS are deliberately not exposed: changing one rebuilds the organization's warehouse models and re-cuts every reported number, which is a decision for a person in the Bellaso app.",
        "x-openai-isConsequential": true,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "account_id": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 128,
                    "description": "The ad account to update, as its warehouse id (e.g. \"facebook_ads_123456\"). Get ids from get_accounts_performance."
                  },
                  "display_name": {
                    "description": "A friendlier label for this account across the app. Cosmetic only: it never changes any metric.",
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                  },
                  "hidden": {
                    "description": "Hide (true) or unhide (false) the account in the app's account pickers and tables. Cosmetic only: hiding does not delete data or stop syncing.",
                    "type": "boolean"
                  }
                },
                "required": [
                  "account_id"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool ran",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/tools/org_update_digest_settings": {
      "post": {
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "tags": [
          "write"
        ],
        "operationId": "call_org_update_digest_settings",
        "summary": "Update Slack digest settings",
        "description": "Set how often this organization's Slack performance digest posts, and optionally which channel it posts to. Setting the frequency to 'off' also stops proactive KPI-change alerts. This changes notification delivery only — it touches no data, no billing and no ad platform.",
        "x-openai-isConsequential": true,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "digest_frequency": {
                    "type": "string",
                    "enum": [
                      "daily",
                      "weekly",
                      "off"
                    ],
                    "description": "How often the Slack performance digest posts: daily, weekly, or off. 'off' is the master switch — it also silences proactive KPI-change alerts."
                  },
                  "digest_channel_id": {
                    "description": "Slack channel id to post into (e.g. \"C08ABCDEF\"), or null to clear it. Omit to leave the current channel unchanged. This is the raw channel id, not the #name — an id that is not in the workspace simply means nothing posts.",
                    "anyOf": [
                      {
                        "type": "string",
                        "minLength": 1,
                        "maxLength": 64,
                        "pattern": "^[A-Za-z0-9_-]+$"
                      },
                      {
                        "type": "null"
                      }
                    ]
                  }
                },
                "required": [
                  "digest_frequency"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool ran",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/tools/signal_page_rule_list": {
      "post": {
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "tags": [
          "read"
        ],
        "operationId": "call_signal_page_rule_list",
        "summary": "List conversion page rules",
        "description": "This organization's conversion page rules: URL patterns that make a pageview count as a conversion (for thank-you pages and other no-form conversions). Read these before adding one — a rule name must be unique per site, and an overlapping pattern double-counts.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "site_id": {
                    "description": "Only return rules for this tracked site.",
                    "type": "string",
                    "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"
                  }
                },
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool ran",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/tools/signal_page_rule_upsert": {
      "post": {
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "tags": [
          "write"
        ],
        "operationId": "call_signal_page_rule_upsert",
        "summary": "Create or update a conversion page rule",
        "description": "Create a page rule, or update an existing one. A matching pageview records a synthetic form submission named by the rule, so it feeds conversion counts, attribution and any Conversions-API destination — write patterns narrowly. Rules are evaluated on every pageview, so only three literal match types exist (exact / prefix / contains) and there is no regex.",
        "x-openai-isConsequential": true,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "id": {
                    "description": "Omit to CREATE a rule; supply it to UPDATE that rule.",
                    "type": "string",
                    "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"
                  },
                  "site_id": {
                    "description": "Which tracked site the rule belongs to (required when creating). A rule never moves between sites — delete and recreate instead.",
                    "type": "string",
                    "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"
                  },
                  "name": {
                    "description": "The conversion name recorded when the rule matches, e.g. \"Quote request\". Must be unique per site.",
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120
                  },
                  "match_type": {
                    "description": "How the pattern is compared: exact and prefix test the URL PATH (so their patterns must start with \"/\"); contains tests the full URL. There is no regex option.",
                    "type": "string",
                    "enum": [
                      "exact",
                      "prefix",
                      "contains"
                    ]
                  },
                  "pattern": {
                    "description": "The path or substring to match, e.g. \"/thank-you\". Validated against the match type — a pattern-only update is checked against the rule's stored match type.",
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 500
                  },
                  "is_active": {
                    "description": "Whether the rule is evaluated on incoming pageviews (defaults to true on create).",
                    "type": "boolean"
                  }
                },
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool ran",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/tools/signal_page_rule_delete": {
      "post": {
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "tags": [
          "write"
        ],
        "operationId": "call_signal_page_rule_delete",
        "summary": "Delete a conversion page rule",
        "description": "Remove a page rule so its pattern stops counting as a conversion. Historical conversions already recorded by the rule are NOT deleted — only future pageviews stop matching.",
        "x-openai-isConsequential": true,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string",
                    "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
                    "description": "The page rule to delete."
                  }
                },
                "required": [
                  "id"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool ran",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/tools/signal_form_group_list": {
      "post": {
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "tags": [
          "read"
        ],
        "operationId": "call_signal_form_group_list",
        "summary": "List form groups",
        "description": "This organization's form groups — sets of step form names treated as ONE form, so a multi-step form counts as a single conversion instead of one per step. Read these before grouping, to see which step names are already claimed.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {},
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool ran",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/tools/signal_form_group_upsert": {
      "post": {
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "tags": [
          "write"
        ],
        "operationId": "call_signal_form_group_upsert",
        "summary": "Create or update a form group",
        "description": "Group the step form names of one multi-step form under a single name, so conversion rules and send-deduplication treat them as one form rather than one conversion per step. Supplying form_names on an update REPLACES the whole list.",
        "x-openai-isConsequential": true,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "id": {
                    "description": "Omit to CREATE a group; supply it to UPDATE that group.",
                    "type": "string",
                    "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"
                  },
                  "name": {
                    "description": "Group name, unique in the organization, e.g. \"Quote wizard\".",
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 80
                  },
                  "form_names": {
                    "description": "The observed form names that are steps of ONE form, e.g. [\"aq-step1\",\"aq-step2\"]. Blanks are dropped and duplicates de-duped; 1–50 unique entries survive.",
                    "minItems": 1,
                    "maxItems": 50,
                    "type": "array",
                    "items": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 256
                    }
                  }
                },
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool ran",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/tools/signal_attribution_model_list": {
      "post": {
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "tags": [
          "read"
        ],
        "operationId": "call_signal_attribution_model_list",
        "summary": "List attribution models",
        "description": "This organization's Signal attribution models, with each model's type, lookback window and whether it is active or the default. Read this before adding one — models are computed side by side, so a near-duplicate mostly adds noise.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {},
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool ran",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/tools/signal_attribution_model_upsert": {
      "post": {
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "tags": [
          "write"
        ],
        "operationId": "call_signal_attribution_model_upsert",
        "summary": "Create or update an attribution model",
        "description": "Create an attribution model, or update one in place. Models are computed ON READ, so changing one re-cuts how credit is reported across channels immediately and retroactively — say which model you changed when you report results. name and model_type are required on both create and update (an update replaces the model's whole definition).",
        "x-openai-isConsequential": true,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "id": {
                    "description": "Omit to CREATE a model; supply it to UPDATE that model.",
                    "type": "string",
                    "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"
                  },
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120,
                    "description": "Model name shown in the Signal attribution views."
                  },
                  "model_type": {
                    "type": "string",
                    "enum": [
                      "first_touch",
                      "last_touch",
                      "linear",
                      "time_decay",
                      "position_based"
                    ],
                    "description": "How credit is split across a contact's touchpoints."
                  },
                  "config": {
                    "description": "Model-specific settings, e.g. { half_life_days: 7 } for time_decay or { first: 0.4, last: 0.4 } for position_based. Unknown keys are stored as-is.",
                    "type": "object",
                    "propertyNames": {
                      "type": "string"
                    },
                    "additionalProperties": {}
                  },
                  "lookback_days": {
                    "description": "How far before a conversion touchpoints still earn credit (1–730, default 90).",
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 730
                  },
                  "is_active": {
                    "description": "Whether the model runs in the standard attribution computation (default true).",
                    "type": "boolean"
                  },
                  "is_default": {
                    "description": "Whether this model is the organization's default view (default false).",
                    "type": "boolean"
                  }
                },
                "required": [
                  "name",
                  "model_type"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool ran",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/tools/signal_destination_update_config": {
      "post": {
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "tags": [
          "write"
        ],
        "operationId": "call_signal_destination_update_config",
        "summary": "Update a conversions destination's config",
        "description": "Adjust an EXISTING Conversions-API destination: its name, event map, lookback window, de-duplication window, and whether it is sending. Pausing (is_enabled false) stops outbound conversion sends immediately; resuming also clears the failure counters. Credentials, platform config and the destination's platform cannot be changed here, and destinations cannot be created or deleted through this API.",
        "x-openai-isConsequential": true,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "destination_id": {
                    "type": "string",
                    "pattern": "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$",
                    "description": "The existing Conversions-API destination to reconfigure."
                  },
                  "name": {
                    "description": "Display name for the destination.",
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 80
                  },
                  "event_map": {
                    "description": "Which platform event each Bellaso event type sends as, e.g. { form_submit: \"Lead\" }. Replaces the whole map.",
                    "type": "object",
                    "propertyNames": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 64
                    },
                    "additionalProperties": {
                      "type": "string",
                      "minLength": 1,
                      "maxLength": 64
                    }
                  },
                  "lookback_days": {
                    "description": "How many days back conversions are eligible to send (1–90).",
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 90
                  },
                  "dedupe_window_hours": {
                    "description": "At most one send per (rule, person) within this many hours; 0 disables de-duplication.",
                    "type": "integer",
                    "minimum": 0,
                    "maximum": 720
                  },
                  "is_enabled": {
                    "description": "Resume (true → status active, which also clears the failure counters) or pause (false → status paused) sending.",
                    "type": "boolean"
                  }
                },
                "required": [
                  "destination_id"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool ran",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/tools/signal_sessions_list": {
      "post": {
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "tags": [
          "read"
        ],
        "operationId": "call_signal_sessions_list",
        "summary": "List Signal sessions",
        "description": "List recent Signal sessions for the organization (operational Supabase state). Optional site_id filter. recorded_only limits to playable replays (ready/partial). Does not return signed playback URLs.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "site_id": {
                    "type": "string",
                    "format": "uuid",
                    "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                  },
                  "recorded_only": {
                    "type": "boolean"
                  },
                  "limit": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 200
                  },
                  "cursor_started_at": {
                    "type": "string",
                    "minLength": 1
                  },
                  "cursor_id": {
                    "type": "string",
                    "format": "uuid",
                    "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                  },
                  "device_class": {
                    "type": "string",
                    "enum": [
                      "mobile",
                      "tablet",
                      "desktop"
                    ]
                  },
                  "client_os": {
                    "type": "string",
                    "enum": [
                      "ios",
                      "android",
                      "macos",
                      "windows",
                      "linux",
                      "chromeos"
                    ]
                  },
                  "page_contains": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 120
                  },
                  "landing_only": {
                    "type": "boolean"
                  },
                  "has_form_submit": {
                    "type": "boolean"
                  }
                },
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool ran",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/tools/signal_session_get": {
      "post": {
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "tags": [
          "read"
        ],
        "operationId": "call_signal_session_get",
        "summary": "Get Signal session",
        "description": "Fetch one Signal session plus its chunk index (hashes and sizes, no signed URLs). Playback grant is a separate tool.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "session_id": {
                    "type": "string",
                    "format": "uuid",
                    "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                  }
                },
                "required": [
                  "session_id"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool ran",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/tools/signal_session_playback_grant": {
      "post": {
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "tags": [
          "read"
        ],
        "operationId": "call_signal_session_playback_grant",
        "summary": "Grant Signal session playback URLs",
        "description": "Mint short-lived signed GET URLs for every chunk of a playable session. Expiry is at most 5 minutes and never past recording_expires_at. Deleted/expired/unavailable sessions return denied.",
        "x-openai-isConsequential": false,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "session_id": {
                    "type": "string",
                    "format": "uuid",
                    "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                  }
                },
                "required": [
                  "session_id"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool ran",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/tools/signal_session_delete": {
      "post": {
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "tags": [
          "write"
        ],
        "operationId": "call_signal_session_delete",
        "summary": "Delete Signal session recording",
        "description": "Soft-delete a Signal session recording immediately (denies further playback). Object cleanup is asynchronous.",
        "x-openai-isConsequential": true,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "session_id": {
                    "type": "string",
                    "format": "uuid",
                    "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                  }
                },
                "required": [
                  "session_id"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool ran",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/v1/tools/signal_session_enqueue_analysis": {
      "post": {
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "tags": [
          "write"
        ],
        "operationId": "call_signal_session_enqueue_analysis",
        "summary": "Enqueue Signal session analysis",
        "description": "Enqueue a budgeted LLM analysis job for a session. Idempotent on (session_id, input_hash, model_version, prompt_version). Honors signal_replay_settings analysis_enabled and monthly USD budget. Does not run the model — only enqueues.",
        "x-openai-isConsequential": true,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$schema": "https://json-schema.org/draft/2020-12/schema",
                "type": "object",
                "properties": {
                  "session_id": {
                    "type": "string",
                    "format": "uuid",
                    "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$"
                  },
                  "model_version": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 128
                  },
                  "prompt_version": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 128
                  }
                },
                "required": [
                  "session_id"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The tool ran",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ToolResult"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "ck_ endpoint token"
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Arguments failed validation",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Missing, malformed, revoked or unknown token",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "PaymentRequired": {
        "description": "The organization does not currently hold the Bellaso agent entitlement",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Forbidden": {
        "description": "The tool refused the call (guardrail)",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "NotFound": {
        "description": "No such tool, or this token may not call it",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "PayloadTooLarge": {
        "description": "Request body exceeds 64 KiB",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "RateLimited": {
        "description": "Per-minute or per-day endpoint limit reached; see the Retry-After header",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "InternalError": {
        "description": "The tool failed to execute",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    },
    "schemas": {
      "ToolResult": {
        "type": "object",
        "required": [
          "ok",
          "tool",
          "data"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          },
          "tool": {
            "type": "string"
          },
          "data": {
            "description": "The tool's payload — byte-identical to what the same tool returns over MCP."
          }
        }
      },
      "ToolSummary": {
        "type": "object",
        "required": [
          "name",
          "title",
          "description",
          "annotations"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "module": {
            "type": [
              "string",
              "null"
            ],
            "description": "Data module the tool reads, or null when ungated."
          },
          "annotations": {
            "type": "object",
            "properties": {
              "readOnlyHint": {
                "type": "boolean"
              },
              "destructiveHint": {
                "type": "boolean"
              },
              "openWorldHint": {
                "type": "boolean"
              }
            }
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error",
          "message"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "Stable machine-readable code (validation_error, unauthorized, subscription_required, not_found, rate_limited, tool_denied, tool_error, payload_too_large, internal_error)."
          },
          "message": {
            "type": "string"
          },
          "path": {
            "type": "string",
            "description": "Argument path that failed validation, when applicable."
          },
          "request_id": {
            "type": "string"
          }
        }
      }
    }
  }
}
