{"openapi":"3.1.0","info":{"title":"Magistry API","version":"1.0.0","description":"Programmatic access to a Magistry store, backed by the same tool registry as the in-dashboard AI operator (Magistry) and the MCP server. Authenticate with an OAuth 2.1 access token or a Magistry API key."},"servers":[{"url":"https://app.magistry.io"}],"components":{"schemas":{"Error":{"type":"object","description":"Every error the API answers with. `error` is a stable machine-readable code — branch on it, never on the prose. `request_id` identifies this one request and resolves to the audit row it wrote; quote it in support.","properties":{"error":{"type":"string","enum":["address_not_allowed","corpus_unavailable","credential_rejected","expired","forbidden","idempotency_conflict","idempotency_in_flight","idempotency_unavailable","insufficient_scope","invalid_body","invalid_client","invalid_request","no_credential","not_found","payload_too_large","rate_limited","rls_scoped_execution_unavailable","server_error","unauthorized","unavailable"],"description":"The stable code. Removing or renaming one is a breaking change."},"message":{"type":"string","description":"For a person. Not part of the contract."},"request_id":{"type":"string"},"docs":{"type":"string","format":"uri"}},"required":["error","message","request_id"]}},"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"Magistry API key (mag_sk_live_…) or OAuth access token"},"oauth2":{"type":"oauth2","flows":{"authorizationCode":{"authorizationUrl":"https://app.magistry.io/api/oauth/authorize","tokenUrl":"https://app.magistry.io/api/oauth/token","scopes":{"read":"Read all your store data.","write":"Make changes across every area (subject to your approval settings).","catalog:read":"Read products, variants, content and pricing state.","catalog:write":"Read and change products, variants, content and pricing state.","ads:read":"Read Google Ads campaigns, budgets, keywords and creatives.","ads:write":"Read and change Google Ads campaigns, budgets, keywords and creatives.","analytics:read":"Read performance, reporting and comparisons.","analytics:write":"Read and change performance, reporting and comparisons.","merchant:read":"Read Merchant Center feeds, product statuses and issues.","merchant:write":"Read and change Merchant Center feeds, product statuses and issues.","orders:read":"Read orders, fulfilments, refunds and edits.","orders:write":"Read and change orders, fulfilments, refunds and edits.","cs:read":"Read customer-service threads and replies.","cs:write":"Read and change customer-service threads and replies.","promotions:read":"Read discounts and promotions.","promotions:write":"Read and change discounts and promotions.","costs:read":"Read product costs and operating expenses.","costs:write":"Read and change product costs and operating expenses.","automation:read":"Read agents, alerts, skills and job triggers.","automation:write":"Read and change agents, alerts, skills and job triggers.","research:read":"Read market/domain research and web lookups.","research:write":"Read and change market/domain research and web lookups.","reputation:read":"Read reviews and mentions.","reputation:write":"Read and change reviews and mentions.","disputes:read":"Read chargeback disputes.","disputes:write":"Read and change chargeback disputes.","admin":"Full access — read and change everything, including account settings."}}}}}},"paths":{"/api/v1/tools/list_store_actions":{"post":{"operationId":"list_store_actions","summary":"Lists the actions Magistry can carry out on this store, each with the exact parameters it takes as a JSON Schema. Call t","description":"Lists the actions Magistry can carry out on this store, each with the exact parameters it takes as a JSON Schema. Call this BEFORE proposing any change: it tells you what a verb is named, what it requires, and which domain it belongs to, so you never have to guess a parameter name. The reply also states how much of the acting surface is exposed versus withheld, so you can tell a short list from a complete one. Read-only — nothing is proposed or executed by calling it.","tags":["read"],"security":[{"bearerAuth":[]},{"oauth2":["read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"domain":{"type":"string","description":"Optional domain filter, e.g. ads_google."}},"required":[]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/get_store_overview":{"post":{"operationId":"get_store_overview","summary":"Returns a high-level snapshot of the current store: name, catalogue counts, open alert count, calibration summary, and l","description":"Returns a high-level snapshot of the current store: name, catalogue counts, open alert count, calibration summary, and last sync time. Use this as the first tool call in any conversation to orient the response. ALWAYS read `agents` before answering anything about what the platform is or is not doing: when an agent is in dry run its executor logs decisions and sends nothing live, which is the single fact that explains a busy store with almost no applied changes. Say so, and give the route in `agents.dry_run[].turn_it_on`, before discussing anything else. `tenant_type` says whether this is a Shopify storefront or a business without one — a business without one has no products, feed or theme, so do not offer them. `decisions` is the whole-store bucket census and is the ONLY honest denominator for \"how much has the platform changed\": most of it is usually `no_change` bookkeeping, so never quote a total as changes made. Catalogue counts come in two vocabularies that answer different questions and do NOT agree: `catalogue.storefront_status` is what Shopify says (active / draft / archived) and is what \"active\" normally means; `catalogue.magistry_lifecycle` is Magistry's own label for how a product is being managed. Quote whichever the question is about, and never read one as the other.","tags":["read"],"security":[{"bearerAuth":[]},{"oauth2":["catalog:read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{},"required":[]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/query_products":{"post":{"operationId":"query_products","summary":"Search and list products in the current store. Supports filtering by lifecycle_state (e.g. \"active\", \"draft\", \"winner\") ","description":"Search and list products in the current store. Supports filtering by lifecycle_state (e.g. \"active\", \"draft\", \"winner\") or whether they have an active pending decision, sorting by recency or ROAS, and limiting results. Returns id, title, state, vendor, price, last decision, and current ROAS.","tags":["read"],"security":[{"bearerAuth":[]},{"oauth2":["catalog:read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"filter":{"type":"object","description":"Optional filters to narrow the product list.","properties":{"lifecycle_state":{"type":"string","description":"Filter to a specific lifecycle state (e.g. \"active\", \"draft\", \"winner\")."},"has_active_decision":{"type":"boolean","description":"If true, only return products with a pending (unapplied) decision."}}},"sort":{"type":"string","enum":["created_desc","updated_desc","roas_desc"],"description":"Sort order. Defaults to updated_desc."},"limit":{"type":"integer","minimum":1,"maximum":50,"description":"Max results to return (default 10, max 50)."}},"required":[]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/query_performance":{"post":{"operationId":"query_performance","summary":"Returns ad performance data aggregated over a time window. Includes totals (revenue, ad spend, ROAS, conversion rate, se","description":"Returns ad performance data aggregated over a time window. Includes totals (revenue, ad spend, ROAS, conversion rate, sessions), top 5 performing products, up to 5 underperformers, and a daily trend array. Optionally filter to a single product by product_id.","tags":["read"],"security":[{"bearerAuth":[]},{"oauth2":["analytics:read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"window_days":{"type":"integer","minimum":1,"maximum":90,"description":"Number of days to look back (default 7, max 90)."},"product_id":{"type":"string","description":"Optional UUID to scope results to a single product."}},"required":[]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/get_recent_decisions":{"post":{"operationId":"get_recent_decisions","summary":"Recent entries from the decision log, each carrying the bucket the Decisions page files it under: live (applied) / queue","description":"Recent entries from the decision log, each carrying the bucket the Decisions page files it under: live (applied) / queued / failed / suggested / reverted / noop (no change). READ THE BUCKET BEFORE DESCRIBING A ROW. Most stores are dominated by \"no change\" bookkeeping — one carries 2,178 of 2,217 — so a list of recent rows is not a list of changes, and calling one a change is wrong by two orders of magnitude. Use `mode` to ask for one bucket: \"suggested\" is what a merchant can still act on, \"queued\" is what runs next. The whole-store counts per bucket are in get_store_overview.decisions — this call returns at most `limit` rows and is never a denominator.","tags":["read"],"security":[{"bearerAuth":[]},{"oauth2":["automation:read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"mode":{"type":"string","enum":["all","live","queued","suggested","reverted","failed","noop"],"description":"Which bucket to return: live (applied), queued, failed, suggested, reverted, noop (no change), or all (default)."},"window_days":{"type":"integer","minimum":1,"maximum":30,"description":"How many days back to look (default 7, max 30). Counts are whole-store."},"to_state":{"type":"string","description":"Optional: filter to a target lifecycle state (e.g. \"DRAFT\", \"WINNER\")."},"applied":{"type":"boolean","description":"Optional: true = only rows that reached Shopify; false = only rows that did not. Prefer `mode` — a row that never reached Shopify is not necessarily a proposal."},"handle":{"type":"string","description":"Optional: one product handle."},"limit":{"type":"integer","minimum":1,"maximum":50,"description":"Max decisions to return (default 10, max 50)."}},"required":[]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/where_in_magistry":{"post":{"operationId":"where_in_magistry","summary":"Where in the Magistry dashboard the merchant goes to act on something, and what they press when they get there. Returns ","description":"Where in the Magistry dashboard the merchant goes to act on something, and what they press when they get there. Returns the page name as it appears in the menu, its URL, the section heading on that page and the exact button text — scoped to THIS tenant, so a surface this business does not have is never suggested. CALL THIS BEFORE TELLING A MERCHANT TO DO ANYTHING IN MAGISTRY. Reading a proposal, an alert or a customer thread tells you WHAT is waiting; only this tells you WHERE it is. Pass `question` in plain language (\"where do I approve a proposal\", \"how do I answer a customer\") to get the ranked answers, or omit it for the whole map.","tags":["read"],"security":[{"bearerAuth":[]},{"oauth2":["read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"question":{"type":"string","description":"What the merchant wants to do, in plain language. Omit to list every destination."}},"required":[]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/get_alerts":{"post":{"operationId":"get_alerts","summary":"Returns active alerts for the current store. By default returns only open (unresolved) alerts ordered by severity and de","description":"Returns active alerts for the current store. By default returns only open (unresolved) alerts ordered by severity and detection time. Use to answer questions like \"what is wrong with my store?\" or \"are there any critical issues?\".","tags":["read"],"security":[{"bearerAuth":[]},{"oauth2":["automation:read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"severity":{"type":"string","enum":["critical","warning","info"],"description":"Optional: filter to a specific severity level."},"status":{"type":"string","enum":["open","all"],"description":"open = unresolved only (default); all = include resolved."},"limit":{"type":"integer","minimum":1,"maximum":50,"description":"Max results (default 20, max 50)."}},"required":[]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/get_calibration":{"post":{"operationId":"get_calibration","summary":"Returns the current self-calibrated performance thresholds for this store. These thresholds (ROAS floors, ceilings, perc","description":"Returns the current self-calibrated performance thresholds for this store. These thresholds (ROAS floors, ceilings, percentiles) drive the classifier and decision engine. Use when explaining why a product was classified a certain way, or when the user asks about their store's performance benchmarks.","tags":["read"],"security":[{"bearerAuth":[]},{"oauth2":["catalog:read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{},"required":[]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/get_cs_attention":{"post":{"operationId":"get_cs_attention","summary":"Returns customer service threads that need attention for the current store. These are conversations flagged by severity ","description":"Returns customer service threads that need attention for the current store. These are conversations flagged by severity (anomaly / sentiment signals), unresolved, most urgent first. Use when asked about customer issues, support volume, or which customers require urgent follow-up.","tags":["read"],"security":[{"bearerAuth":[]},{"oauth2":["cs:read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"limit":{"type":"integer","minimum":1,"maximum":20,"description":"Max threads to return (default 10, max 20)."}},"required":[]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/get_variants":{"post":{"operationId":"get_variants","summary":"Returns variants for a specific product, or lists the top variants by price across the store. Use when the user asks abo","description":"Returns variants for a specific product, or lists the top variants by price across the store. Use when the user asks about sizes, colours, SKUs, inventory levels, or specific variant pricing.","tags":["read"],"security":[{"bearerAuth":[]},{"oauth2":["catalog:read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"product_id":{"type":"string","description":"UUID of the product to fetch variants for. Omit to list variants across the store."},"limit":{"type":"integer","minimum":1,"maximum":50,"description":"Max variants to return (default 10, max 50)."}},"required":[]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/get_product_costs":{"post":{"operationId":"get_product_costs","summary":"Returns cost data and margin analysis for products. Confidence tier A = verified Shopify COGS, B = reverse-image AliExpr","description":"Returns cost data and margin analysis for products. Confidence tier A = verified Shopify COGS, B = reverse-image AliExpress estimate, C = unknown. Use when the user asks about margins, costs, profitability, or which products have unverified costs.","tags":["read"],"security":[{"bearerAuth":[]},{"oauth2":["costs:read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"product_ids":{"type":"array","items":{"type":"string","format":"uuid"},"maxItems":50,"description":"Optional list of product UUIDs to filter to (max 50). Omit to return latest costs across the store."},"confidence_tier":{"type":"string","enum":["A","B","C"],"description":"Optional: filter to a specific cost confidence tier."}},"required":[]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/get_orders_summary":{"post":{"operationId":"get_orders_summary","summary":"Returns a summary of recent orders aggregated from performance data. Includes total orders, total revenue, average order","description":"Returns a summary of recent orders aggregated from performance data. Includes total orders, total revenue, average order value, refund amount, and the top 5 products by order count. Use when the user asks about sales volume, order trends, or top-selling products.","tags":["read"],"security":[{"bearerAuth":[]},{"oauth2":["orders:read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"window_days":{"type":"integer","minimum":1,"maximum":90,"description":"Number of days to look back (default 14, max 90)."}},"required":[]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/get_research_candidates":{"post":{"operationId":"get_research_candidates","summary":"Returns potential winning products discovered by the research pipeline. Each candidate has a score, signals (ad duration","description":"Returns potential winning products discovered by the research pipeline. Each candidate has a score, signals (ad duration, cross-store count, review velocity), and a status. Use when the user asks about new product opportunities, research results, or what products to onboard next.","tags":["read"],"security":[{"bearerAuth":[]},{"oauth2":["research:read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["discovered","scored","verified","onboarded","rejected"],"description":"Optional: filter by candidate status. Omit to see all active candidates."},"limit":{"type":"integer","minimum":1,"maximum":30,"description":"Max candidates to return (default 10, max 30)."}},"required":[]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/get_job_runs":{"post":{"operationId":"get_job_runs","summary":"Returns recent worker job run statuses for the current store. Use to diagnose why a sync, classify, or decide job did no","description":"Returns recent worker job run statuses for the current store. Use to diagnose why a sync, classify, or decide job did not run, or to check the health of scheduled background tasks. Filters by time window, job name, or status (running/succeeded/failed).","tags":["read"],"security":[{"bearerAuth":[]},{"oauth2":["automation:read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"window_hours":{"type":"integer","minimum":1,"maximum":168,"description":"How many hours back to look (default 24, max 168 = 7 days)."},"job_name":{"type":"string","description":"Optional: filter to a specific job name (e.g. \"sync-catalog\", \"classify\", \"decide\")."},"status":{"type":"string","enum":["running","succeeded","failed"],"description":"Optional: filter to a specific run status."}},"required":[]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/get_discount_history":{"post":{"operationId":"get_discount_history","summary":"Returns recent discount campaigns and their measured outcomes. Shows which products were discounted, the discount percen","description":"Returns recent discount campaigns and their measured outcomes. Shows which products were discounted, the discount percentage, conversion lift, and whether the discount is still active. Use when the user asks about discounts, promotions, pricing experiments, or their results.","tags":["read"],"security":[{"bearerAuth":[]},{"oauth2":["promotions:read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"window_days":{"type":"integer","minimum":1,"maximum":90,"description":"How many days back to look (default 30, max 90)."},"active_only":{"type":"boolean","description":"When true, only return currently active discounts (ended_at is null). Default false."}},"required":[]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/get_content_versions":{"post":{"operationId":"get_content_versions","summary":"Returns recent product copy rewrite history — title, description, and meta changes made by the optimizer. Shows what cha","description":"Returns recent product copy rewrite history — title, description, and meta changes made by the optimizer. Shows what changed, when it went live, and measured performance lift where available. Use when the user asks about copy changes, A/B tests, or what the optimizer has rewritten.","tags":["read"],"security":[{"bearerAuth":[]},{"oauth2":["catalog:read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"product_id":{"type":"string","description":"Optional UUID to scope results to a single product."},"window_days":{"type":"integer","minimum":1,"maximum":90,"description":"How many days back to look (default 14, max 90)."},"limit":{"type":"integer","minimum":1,"maximum":50,"description":"Max versions to return (default 10, max 50)."}},"required":[]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/get_cs_threads":{"post":{"operationId":"get_cs_threads","summary":"Returns customer service conversation threads for the current store. Each thread aggregates one email conversation with ","description":"Returns customer service conversation threads for the current store. Each thread aggregates one email conversation with category, sentiment, message count, and an AI summary. Use when the user asks about customer complaints, support trends, or specific conversation threads.","tags":["read"],"security":[{"bearerAuth":[]},{"oauth2":["cs:read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["open","closed","all"],"description":"open = attention not resolved (default); closed = attention resolved; all = no filter."},"sentiment":{"type":"string","enum":["positive","neutral","negative"],"description":"Optional: filter threads by sentiment. positive ≥ 0.2, negative ≤ -0.2, neutral in between."},"limit":{"type":"integer","minimum":1,"maximum":30,"description":"Max threads to return (default 10, max 30)."}},"required":[]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/get_product_detail":{"post":{"operationId":"get_product_detail","summary":"Fetch a complete profile of a single product: title, lifecycle state, variants (with price/cost/inventory), latest cost-","description":"Fetch a complete profile of a single product: title, lifecycle state, variants (with price/cost/inventory), latest cost-confidence tier, recent decisions, active content version, and revenue/orders totals. Use when the user asks about one specific product by id or after a tool result returned that product's id.","tags":["read"],"security":[{"bearerAuth":[]},{"oauth2":["catalog:read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"product_id":{"type":"string","format":"uuid","description":"UUID of the product to inspect."}},"required":["product_id"]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/compare_periods":{"post":{"operationId":"compare_periods","summary":"Compare aggregate performance between two adjacent time windows. Returns revenue, orders, spend, ROAS, and conversion-ra","description":"Compare aggregate performance between two adjacent time windows. Returns revenue, orders, spend, ROAS, and conversion-rate totals for the current period and the prior period of equal length, plus deltas. Window options: \"week\" (last 7d vs 7d before), \"month\" (30d vs 30d), \"custom\" (caller supplies days).","tags":["read"],"security":[{"bearerAuth":[]},{"oauth2":["analytics:read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"window":{"type":"string","enum":["week","month","custom"],"description":"Length of each comparison period. Default: week."},"days":{"type":"integer","minimum":1,"maximum":180,"description":"Period length in days when window === \"custom\"."}},"required":[]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/find_similar_products":{"post":{"operationId":"find_similar_products","summary":"Find products most similar to a seed product using image-text embedding cosine similarity (pgvector 1536-dim). Scoped to","description":"Find products most similar to a seed product using image-text embedding cosine similarity (pgvector 1536-dim). Scoped to the current store. Useful for finding cannibalization candidates, near-duplicates, or category siblings. The seed product must already have an embedding (populated by the embeddings worker job).","tags":["read"],"security":[{"bearerAuth":[]},{"oauth2":["catalog:read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"product_id":{"type":"string","format":"uuid","description":"UUID of the seed product."},"limit":{"type":"integer","minimum":1,"maximum":20,"description":"How many neighbors to return (default 8)."}},"required":["product_id"]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/get_cs_thread":{"post":{"operationId":"get_cs_thread","summary":"Fetch the full body of a customer-service thread: subject, all messages (direction, sender, body text, sentiment, urgenc","description":"Fetch the full body of a customer-service thread: subject, all messages (direction, sender, body text, sentiment, urgency), AI summary, matched customer + product, and attention metadata. Use when a specific thread is referenced by id; never for browsing (use get_cs_attention for that).","tags":["read"],"security":[{"bearerAuth":[]},{"oauth2":["cs:read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"thread_id":{"type":"string","format":"uuid","description":"UUID of the cs_threads row."}},"required":["thread_id"]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/query_cannibalization":{"post":{"operationId":"query_cannibalization","summary":"List image-similarity clusters where multiple products are competing for the same demand. Each cluster includes its memb","description":"List image-similarity clusters where multiple products are competing for the same demand. Each cluster includes its members (product titles, revenue_30d, lifecycle_state) so Magistry can identify the dominant performer and propose drafting the weaker ones.","tags":["read"],"security":[{"bearerAuth":[]},{"oauth2":["analytics:read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"limit":{"type":"integer","minimum":1,"maximum":30,"description":"Maximum clusters to return (default 10)."}},"required":[]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/get_ad_performance":{"post":{"operationId":"get_ad_performance","summary":"Get Google Ads KPIs (spend, conversions, ROAS, CPA) for the store over the requested window, plus the top campaigns by s","description":"Get Google Ads KPIs (spend, conversions, ROAS, CPA) for the store over the requested window, plus the top campaigns by spend with their per-campaign metrics. Use to answer ad-performance questions or compare ad health across windows.","tags":["read"],"security":[{"bearerAuth":[]},{"oauth2":["ads:read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"days":{"type":"integer","minimum":1,"maximum":180,"description":"Look-back window in days (default 30)."},"campaign_limit":{"type":"integer","minimum":1,"maximum":50,"description":"How many campaigns to return ranked by spend (default 15)."}},"required":[]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/get_marketing_brain":{"post":{"operationId":"get_marketing_brain","summary":"Returns the current state of the store's autonomous Brain (surfaced in the app as \"Brain\", formerly \"Marketing Brain\"): ","description":"Returns the current state of the store's autonomous Brain (surfaced in the app as \"Brain\", formerly \"Marketing Brain\"): its operating mode + autonomy + autopilot scope; its latest situational read (the Brain's own summary of what's going on); the open cross-domain proposals it has made (ads / catalog / pricing / promotions / feed / social — each with action, confidence, hypothesis and reversibility); recent measured outcomes (win / neutral / loss verdicts); and the budget envelope. Use whenever asked what the Brain is doing, why it proposed something, what is pending approval, whether autopilot is on / how it is scoped, or how its moves have performed.","tags":["read"],"security":[{"bearerAuth":[]},{"oauth2":["ads:read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{},"required":[]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/lookup_domain_research":{"post":{"operationId":"lookup_domain_research","summary":"List recent domain-name research runs (search prompt + result counts), or drill into one specific run to retrieve its to","description":"List recent domain-name research runs (search prompt + result counts), or drill into one specific run to retrieve its top candidate domains and their availability + SEO scores. Pass search_id to drill in.","tags":["read"],"security":[{"bearerAuth":[]},{"oauth2":["research:read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"search_id":{"type":"string","format":"uuid","description":"UUID of a specific search to drill into. If omitted, returns the recent searches list."},"result_limit":{"type":"integer","minimum":1,"maximum":40,"description":"Max results to return when search_id is supplied (default 20)."},"search_limit":{"type":"integer","minimum":1,"maximum":20,"description":"Max searches to list when search_id is omitted (default 10)."}},"required":[]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/get_promotion_detail":{"post":{"operationId":"get_promotion_detail","summary":"Fetch a single promotion with its targeted product/variant list, discount terms, schedule, and status. Use when the user","description":"Fetch a single promotion with its targeted product/variant list, discount terms, schedule, and status. Use when the user references a specific promotion or asks what products are in a campaign.","tags":["read"],"security":[{"bearerAuth":[]},{"oauth2":["promotions:read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"promotion_id":{"type":"string","format":"uuid","description":"UUID of the promotions row."}},"required":["promotion_id"]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/get_competitor_ad_intel":{"post":{"operationId":"get_competitor_ad_intel","summary":"What the store's tracked competitors are actively advertising on Meta: top headlines, CTAs, longest-running ad angles, l","description":"What the store's tracked competitors are actively advertising on Meta: top headlines, CTAs, longest-running ad angles, landing domains and a spend ESTIMATE (never a measured figure). Use to answer \"what are competitors running / spending behind?\" or to inform creative angles. Pass competitor_domain to drill into one rival's individual ads. Empty until the competitor ad-intel job has run and a Meta token is configured.","tags":["read"],"security":[{"bearerAuth":[]},{"oauth2":["ads:read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"competitor_domain":{"type":"string","description":"Optional tracked-competitor domain to drill into (returns individual creatives)."},"limit":{"type":"integer","minimum":1,"maximum":20,"description":"How many competitors (or creatives, when drilling in) to return. Default 8."}},"required":[]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/get_site_overview":{"post":{"operationId":"get_site_overview","summary":"Marketing-website surface (standard tenants): magistry-front-site telemetry, DB-driven landing pages, and A/B experiment","description":"Marketing-website surface (standard tenants): magistry-front-site telemetry, DB-driven landing pages, and A/B experiments. Returns traffic + funnel totals for the window (pageviews, sessions, visitors, CTA / signup / demo clicks, signup conversion rate), the top paths by pageviews with per-path CVR, top traffic sources, published and draft landing pages (/lp/{slug}), and site experiments with arms and winners. Use when asked how the front website / marketing site is doing, which landing pages exist or convert, or how a site experiment is going. This is website telemetry, not Shopify storefront analytics.","tags":["read"],"security":[{"bearerAuth":[]},{"oauth2":["read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"days":{"type":"integer","minimum":7,"maximum":90,"description":"Lookback window in days (default 30)."}},"required":[]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/get_organic_search":{"post":{"operationId":"get_organic_search","summary":"Organic search performance from Google Search Console: clicks, impressions, CTR and impression-weighted average position","description":"Organic search performance from Google Search Console: clicks, impressions, CTR and impression-weighted average position for the window versus the prior one, the queries people actually type, the pages that earn (or lose) the clicks, sitemap health, and the derived opportunity queue (striking-distance queries, CTR gaps, rank decay, cannibalisation, index loss, new demand, content gaps, paid-vs-organic overlap). Pass `page` to scope everything to one URL or path — that also returns the queries that page ranks for and its index status. Use for any question about SEO, organic traffic, rankings, what people search for, why traffic moved, or which page to improve. NOTE: Search Console settles data ~2 days late and withholds rare queries, so `data_through` and `coverage_ratio` are returned and should be quoted rather than ignored — query-level totals are a floor, not the whole picture.","tags":["read"],"security":[{"bearerAuth":[]},{"oauth2":["read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"days":{"type":"integer","minimum":7,"maximum":180,"description":"Lookback window in days (default 28)."},"page":{"type":"string","description":"Optional path or URL to scope to, e.g. \"/products/blue-dress\". Apex and www variants of the same page are treated as one."},"limit":{"type":"integer","minimum":1,"maximum":100,"description":"Max rows per list (default 25)."}},"required":[]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/web_read":{"post":{"operationId":"web_read","summary":"Fetch a public URL and return its main content as clean text/markdown (via the Reach gateway). Use to read a competitor ","description":"Fetch a public URL and return its main content as clean text/markdown (via the Reach gateway). Use to read a competitor page, article, or spec when you need the actual on-page content. Returns { text } or found=false.","tags":["read"],"security":[{"bearerAuth":[]},{"oauth2":["research:read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string","description":"Public http(s) URL to fetch."}},"required":["url"]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/web_search":{"post":{"operationId":"web_search","summary":"Search the open web for fresh sources (via the Reach gateway / Exa). Use for competitor discovery, market lookups, or an","description":"Search the open web for fresh sources (via the Reach gateway / Exa). Use for competitor discovery, market lookups, or anything past the training cutoff. Returns hits with url/title/snippet. Empty array means web search is not configured for this deployment — say so rather than inventing results.","tags":["read"],"security":[{"bearerAuth":[]},{"oauth2":["research:read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"query":{"type":"string","description":"Search query."},"limit":{"type":"integer","minimum":1,"maximum":20,"description":"Max hits (default 8)."}},"required":["query"]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/get_orders":{"post":{"operationId":"get_orders","summary":"Lists individual recent orders (amount, currency, time, and the ad source they were attributed to). Use for order-level ","description":"Lists individual recent orders (amount, currency, time, and the ad source they were attributed to). Use for order-level detail; use get_orders_summary for aggregates and top products.","tags":["read"],"security":[{"bearerAuth":[]},{"oauth2":["orders:read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"window_days":{"type":"integer","minimum":1,"maximum":365,"description":"Days back to look (default 30)."},"limit":{"type":"integer","minimum":1,"maximum":100,"description":"Max orders (default 20, max 100)."}},"required":[]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/get_disputes":{"post":{"operationId":"get_disputes","summary":"Lists chargebacks / payment disputes for the store: type, network, reason, amount, status, evidence deadline, the system","description":"Lists chargebacks / payment disputes for the store: type, network, reason, amount, status, evidence deadline, the system’s fight recommendation + estimated win probability, and the customer. Use for dispute-defense questions.","tags":["read"],"security":[{"bearerAuth":[]},{"oauth2":["disputes:read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","description":"Optional: filter by dispute status (e.g. \"needs_response\", \"won\", \"lost\")."},"limit":{"type":"integer","minimum":1,"maximum":50,"description":"Max disputes (default 20)."}},"required":[]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/get_expenses":{"post":{"operationId":"get_expenses","summary":"Lists the store’s operating expenses: name, vendor, amount, currency, billing frequency and period. Use for questions ab","description":"Lists the store’s operating expenses: name, vendor, amount, currency, billing frequency and period. Use for questions about overhead, fixed costs and profitability inputs.","tags":["read"],"security":[{"bearerAuth":[]},{"oauth2":["costs:read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"limit":{"type":"integer","minimum":1,"maximum":100,"description":"Max expenses (default 50)."},"include_archived":{"type":"boolean","description":"Include archived expenses (default false)."}},"required":[]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/get_ad_campaigns":{"post":{"operationId":"get_ad_campaigns","summary":"Lists advertising campaigns across connected platforms (Google, Meta, TikTok, etc.): platform, name, status, objective, ","description":"Lists advertising campaigns across connected platforms (Google, Meta, TikTok, etc.): platform, name, status, objective, daily budget, and ROAS/CPA targets. Use for questions about what campaigns are running and how they are configured.","tags":["read"],"security":[{"bearerAuth":[]},{"oauth2":["ads:read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"platform":{"type":"string","description":"Optional: filter by platform (e.g. \"google\", \"meta\", \"tiktok\")."},"status":{"type":"string","description":"Optional: filter by campaign status (e.g. \"enabled\", \"paused\")."},"limit":{"type":"integer","minimum":1,"maximum":100,"description":"Max campaigns (default 50)."}},"required":[]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/get_reviews":{"post":{"operationId":"get_reviews","summary":"Lists customer reviews (via The Shopinion) for the store: rating, title, body, reviewer, verified-purchase flag, and the","description":"Lists customer reviews (via The Shopinion) for the store: rating, title, body, reviewer, verified-purchase flag, and the product reviewed. Filter by rating to surface complaints (low) or testimonials (high).","tags":["read"],"security":[{"bearerAuth":[]},{"oauth2":["reputation:read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"min_rating":{"type":"integer","minimum":1,"maximum":5,"description":"Only reviews at or above this rating."},"max_rating":{"type":"integer","minimum":1,"maximum":5,"description":"Only reviews at or below this rating (e.g. 2 for complaints)."},"limit":{"type":"integer","minimum":1,"maximum":50,"description":"Max reviews (default 20)."}},"required":[]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/propose_discount":{"post":{"operationId":"propose_discount","summary":"Propose a percentage discount across one or more products. Returns a plan-card row the user must review and approve befo","description":"Propose a percentage discount across one or more products. Returns a plan-card row the user must review and approve before any Shopify write occurs. Never executes the discount directly.","tags":["propose_write"],"security":[{"bearerAuth":[]},{"oauth2":["promotions:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"product_ids":{"type":"array","items":{"type":"string","format":"uuid"},"minItems":1,"maxItems":50,"description":"UUIDs of the products to discount."},"discount_percent":{"type":"number","minimum":1,"maximum":90,"description":"Percentage to discount (e.g. 20 = 20% off)."},"reason":{"type":"string","maxLength":500,"description":"Human-readable reason for this proposed discount."}},"required":["product_ids","discount_percent","reason"]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/propose_state_transition":{"post":{"operationId":"propose_state_transition","summary":"Propose moving one or more products to a new lifecycle state (e.g. WINNER, DRAFT, CULL). Returns a plan-card the user mu","description":"Propose moving one or more products to a new lifecycle state (e.g. WINNER, DRAFT, CULL). Returns a plan-card the user must approve before any change is applied. Never writes to decision_log or Shopify directly.","tags":["propose_write"],"security":[{"bearerAuth":[]},{"oauth2":["catalog:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"product_ids":{"type":"array","items":{"type":"string","format":"uuid"},"minItems":1,"maxItems":50,"description":"UUIDs of the products to transition."},"to_state":{"type":"string","enum":["WINNER","OPTIMIZE","DISCOUNT","DRAFT","CULL","SEASONAL_VAULT"],"description":"Target lifecycle state."},"reason":{"type":"string","maxLength":500,"description":"Human-readable justification for the transition."}},"required":["product_ids","to_state","reason"]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/propose_draft":{"post":{"operationId":"propose_draft","summary":"Propose soft-removing (drafting) one or more products by transitioning them to the DRAFT lifecycle state. Convenience wr","description":"Propose soft-removing (drafting) one or more products by transitioning them to the DRAFT lifecycle state. Convenience wrapper around propose_state_transition for the DRAFT target. Returns a plan-card the user must approve — never executes directly.","tags":["propose_write"],"security":[{"bearerAuth":[]},{"oauth2":["catalog:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"product_ids":{"type":"array","items":{"type":"string","format":"uuid"},"minItems":1,"maxItems":50,"description":"UUIDs of the products to draft."},"reason":{"type":"string","maxLength":500,"description":"Human-readable reason for drafting these products."}},"required":["product_ids","reason"]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/create_alert":{"post":{"operationId":"create_alert","summary":"Create a metric-threshold automation that fires an alert when a KPI crosses a boundary. Supported metrics: roas, convers","description":"Create a metric-threshold automation that fires an alert when a KPI crosses a boundary. Supported metrics: roas, conversion_rate, sessions, margin. The worker polls active automations on a per-store cron schedule.","tags":["auto_write"],"security":[{"bearerAuth":[]},{"oauth2":["automation:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","maxLength":100,"description":"Short human-readable name for the alert rule."},"trigger_metric":{"type":"string","enum":["roas","conversion_rate","sessions","margin"],"description":"KPI to monitor."},"operator":{"type":"string","enum":["<",">","<=",">="],"description":"Comparison operator applied to the threshold."},"threshold":{"type":"number","description":"Numeric threshold value (e.g. 2.0 for ROAS, 0.03 for CR)."},"scope":{"type":"string","enum":["product","store"],"description":"\"store\" evaluates the aggregate metric; \"product\" fires per-product."},"min_sessions":{"type":"integer","minimum":0,"description":"Optional minimum session count required before the alert can fire."},"notify_via":{"type":"string","enum":["email","in_app"],"description":"Notification channel (default: in_app)."}},"required":["name","trigger_metric","operator","threshold","scope"]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/list_automations":{"post":{"operationId":"list_automations","summary":"List automations configured for the current store. By default only returns enabled automations. Returns id, name, a huma","description":"List automations configured for the current store. By default only returns enabled automations. Returns id, name, a human-readable trigger summary, action summary, enabled flag, and created_at.","tags":["read"],"security":[{"bearerAuth":[]},{"oauth2":["automation:read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"enabled_only":{"type":"boolean","description":"When true (default), only return enabled automations."}},"required":[]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/disable_automation":{"post":{"operationId":"disable_automation","summary":"Disable an existing automation rule so it no longer fires. The row is kept for history; use list_automations to retrieve","description":"Disable an existing automation rule so it no longer fires. The row is kept for history; use list_automations to retrieve it later.","tags":["auto_write"],"security":[{"bearerAuth":[]},{"oauth2":["automation:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"automation_id":{"type":"string","format":"uuid","description":"UUID of the automation to disable."}},"required":["automation_id"]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/compile_workflow_from_nl":{"post":{"operationId":"compile_workflow_from_nl","summary":"Translate a plain-English workflow description into a structured automation (trigger + action) and create a plan card th","description":"Translate a plain-English workflow description into a structured automation (trigger + action) and create a plan card the user must approve. Examples: \"every Monday at 9am alert me if ROAS dropped >30% week-over-week\", \"when open alerts exceed 20 send me an email\". If the description is ambiguous, returns clarification questions instead of creating the plan card.","tags":["propose_write"],"security":[{"bearerAuth":[]},{"oauth2":["automation:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"nl_description":{"type":"string","minLength":8,"maxLength":800,"description":"Plain-English description of the desired automation."}},"required":["nl_description"]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/enable_automation":{"post":{"operationId":"enable_automation","summary":"Re-enable a previously disabled automation. Flips automations.enabled to true. Reversible via disable_automation.","description":"Re-enable a previously disabled automation. Flips automations.enabled to true. Reversible via disable_automation.","tags":["auto_write"],"security":[{"bearerAuth":[]},{"oauth2":["automation:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"automation_id":{"type":"string","format":"uuid","description":"UUID of the automation to enable."}},"required":["automation_id"]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/run_calibration":{"post":{"operationId":"run_calibration","summary":"Re-run self-calibration of this store's decision thresholds (winner/loser floors, etc.) using its own performance distri","description":"Re-run self-calibration of this store's decision thresholds (winner/loser floors, etc.) using its own performance distribution. Idempotent and reversible — the prior thresholds remain in stores.calibration.history.","tags":["auto_write"],"security":[{"bearerAuth":[]},{"oauth2":["automation:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{},"required":[]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/run_classification":{"post":{"operationId":"run_classification","summary":"Re-classify every product's performance tier based on current thresholds and 30-day metrics. Side-effect: products.perfo","description":"Re-classify every product's performance tier based on current thresholds and 30-day metrics. Side-effect: products.performance_tier is updated. Reversible by re-running classification.","tags":["auto_write"],"security":[{"bearerAuth":[]},{"oauth2":["automation:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{},"required":[]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/run_decide_dry":{"post":{"operationId":"run_decide_dry","summary":"Run the decision engine in DRY-RUN mode. Produces decision_log rows with applied_to_shopify=false so Magistry can show w","description":"Run the decision engine in DRY-RUN mode. Produces decision_log rows with applied_to_shopify=false so Magistry can show what would happen without touching Shopify. Use freely to preview the next cycle.","tags":["auto_write"],"security":[{"bearerAuth":[]},{"oauth2":["automation:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{},"required":[]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/run_sync_catalog":{"post":{"operationId":"run_sync_catalog","summary":"Pull the latest product + variant metadata from Shopify. Read-side side-effect only: refreshes our cache of titles, pric","description":"Pull the latest product + variant metadata from Shopify. Read-side side-effect only: refreshes our cache of titles, prices, tags, etc. Does not mutate Shopify.","tags":["auto_write"],"security":[{"bearerAuth":[]},{"oauth2":["automation:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{},"required":[]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/run_sync_orders":{"post":{"operationId":"run_sync_orders","summary":"Pull recent orders + line items from Shopify and refresh performance aggregates. days defaults to 7. Does not mutate Sho","description":"Pull recent orders + line items from Shopify and refresh performance aggregates. days defaults to 7. Does not mutate Shopify.","tags":["auto_write"],"security":[{"bearerAuth":[]},{"oauth2":["automation:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"days":{"type":"number"}},"required":[]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/run_embeddings":{"post":{"operationId":"run_embeddings","summary":"Compute or refresh OpenAI text-embedding-3-small (1536-dim) vectors for products that lack them. Required before find_si","description":"Compute or refresh OpenAI text-embedding-3-small (1536-dim) vectors for products that lack them. Required before find_similar_products or cannibalization work for newly-synced products.","tags":["auto_write"],"security":[{"bearerAuth":[]},{"oauth2":["automation:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{},"required":[]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/run_anomaly_check":{"post":{"operationId":"run_anomaly_check","summary":"Detect performance anomalies (sudden CTR/CR drops, unusual spend) and surface them as alerts. Does not modify products.","description":"Detect performance anomalies (sudden CTR/CR drops, unusual spend) and surface them as alerts. Does not modify products.","tags":["auto_write"],"security":[{"bearerAuth":[]},{"oauth2":["automation:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{},"required":[]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/run_supplier_health":{"post":{"operationId":"run_supplier_health","summary":"Score suppliers based on quality/availability/lead-time signals. Updates supplier_health_checks rows. Reversible by re-r","description":"Score suppliers based on quality/availability/lead-time signals. Updates supplier_health_checks rows. Reversible by re-running.","tags":["auto_write"],"security":[{"bearerAuth":[]},{"oauth2":["automation:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{},"required":[]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/run_cannibalization":{"post":{"operationId":"run_cannibalization","summary":"Re-cluster products by image-text similarity and update product_clusters. Use this after large bulk syncs or when the us","description":"Re-cluster products by image-text similarity and update product_clusters. Use this after large bulk syncs or when the user asks about overlapping products.","tags":["auto_write"],"security":[{"bearerAuth":[]},{"oauth2":["automation:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{},"required":[]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/run_research":{"post":{"operationId":"run_research","summary":"Kick off a research-pipeline pass: scans configured lanes (Meta Ad Library, AliExpress bestsellers, etc.) for new candid","description":"Kick off a research-pipeline pass: scans configured lanes (Meta Ad Library, AliExpress bestsellers, etc.) for new candidate products. Results are persisted as candidates; with the add_new_products automation on, approved candidates are auto-created in Shopify as DRAFT products (never active).","tags":["auto_write"],"security":[{"bearerAuth":[]},{"oauth2":["automation:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{},"required":[]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/run_ab_resolve":{"post":{"operationId":"run_ab_resolve","summary":"Resolve any A/B tests whose statistical power has reached threshold. Updates ab_tests.resolution; does NOT change conten","description":"Resolve any A/B tests whose statistical power has reached threshold. Updates ab_tests.resolution; does NOT change content. Reversible.","tags":["auto_write"],"security":[{"bearerAuth":[]},{"oauth2":["automation:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{},"required":[]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/save_skill":{"post":{"operationId":"save_skill","summary":"Save a reusable prompt as a Magistry Skill so the user can re-run it via the /skill-name slash menu in the chat input. T","description":"Save a reusable prompt as a Magistry Skill so the user can re-run it via the /skill-name slash menu in the chat input. The prompt_template may contain {variable} placeholders that match the parameters list. Scope options: \"user\" (private), \"store\" (any store member can run), or \"workspace\" (any of the user's stores). Skill names must be lowercase-dash format and unique per (owner, scope, store).","tags":["auto_write"],"security":[{"bearerAuth":[]},{"oauth2":["automation:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","pattern":"^[a-z0-9-]+$","maxLength":60,"description":"Unique slug for the skill (e.g. \"weekly-review\")."},"description":{"type":"string","maxLength":280,"description":"Short human-readable description shown in the skill picker."},"prompt_template":{"type":"string","maxLength":8000,"description":"The prompt body. May reference {variable} placeholders that match the parameters list."},"parameters":{"type":"array","maxItems":8,"items":{"type":"object","properties":{"name":{"type":"string","maxLength":40},"description":{"type":"string","maxLength":200},"required":{"type":"boolean"}},"required":["name"]},"description":"Variables the user fills in when running the skill."},"scope":{"type":"string","enum":["user","store","workspace"],"description":"Who can run this skill (default user)."},"default_mode":{"type":"string","enum":["fast","smart","deep"],"description":"Which chat mode to invoke the skill under."}},"required":["name","prompt_template"]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/list_skills":{"post":{"operationId":"list_skills","summary":"List Magistry Skills available to the user — saved prompts that can be re-invoked via /skill-name. Returns name, descrip","description":"List Magistry Skills available to the user — saved prompts that can be re-invoked via /skill-name. Returns name, description, scope, parameter list, and last-used time. Use when the user asks \"what skills do I have?\" or before invoking run_skill.","tags":["read"],"security":[{"bearerAuth":[]},{"oauth2":["automation:read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"scope":{"type":"string","enum":["all","user","store","workspace"],"description":"Filter by scope. Default \"all\" returns everything visible."}},"required":[]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/delete_skill":{"post":{"operationId":"delete_skill","summary":"Delete one of the user's saved Skills by id. Only the owner can delete. Reversible by re-running save_skill with the sam","description":"Delete one of the user's saved Skills by id. Only the owner can delete. Reversible by re-running save_skill with the same name.","tags":["auto_write"],"security":[{"bearerAuth":[]},{"oauth2":["automation:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"skill_id":{"type":"string","format":"uuid","description":"UUID of the otto_skills row to delete."}},"required":["skill_id"]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/ask_user_question":{"post":{"operationId":"ask_user_question","summary":"Ask the user a question and present a small set of options as clickable buttons — a multiple-choice menu. Use this whene","description":"Ask the user a question and present a small set of options as clickable buttons — a multiple-choice menu. Use this whenever a decision is genuinely the user's to make and the answer is one of a few clear choices (confirming an approach, mapping values, yes/no with context, picking between options). Prefer it over a free-text question when you can enumerate the choices. Give 2–6 concise options, each a short label with an optional one-line description. Set multi_select=true when the user may pick several. The user's selection arrives as their next message — after calling this, STOP and wait for it; do not assume an answer.","tags":["read"],"security":[{"bearerAuth":[]},{"oauth2":["read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"question":{"type":"string","maxLength":500,"description":"The question to ask."},"options":{"type":"array","minItems":2,"maxItems":6,"description":"The choices to offer.","items":{"type":"object","properties":{"label":{"type":"string","maxLength":60,"description":"Short button label."},"description":{"type":"string","maxLength":200,"description":"Optional one-line explanation."},"value":{"type":"string","maxLength":120,"description":"The exact token to pass on as a tool argument if this option is picked, when that differs from the label. Another tool that hands you options with a `value` gives you this verbatim — pass it through unchanged; never retype or translate it."}},"required":["label"]}},"multi_select":{"type":"boolean","description":"Allow selecting more than one option. Default false."}},"required":["question","options"]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/render_chart":{"post":{"operationId":"render_chart","summary":"Draw a chart in the conversation. Use it whenever the answer is a shape rather than a number: a trend over time, a compa","description":"Draw a chart in the conversation. Use it whenever the answer is a shape rather than a number: a trend over time, a comparison across products or campaigns, a split of a total, a relationship between two measures. Prefer a chart to a markdown table for anything with more than about five rows of numbers, and always pass the figures you actually read from a tool — never invent or round data to make a nicer picture. Pick the kind by the question: line/area for a value over time; bar for magnitude across categories; stacked_bar or stacked_area when the TOTAL is also a real quantity; pie or donut for one whole split into a few parts; scatter for two measures against each other; combo for one measure as bars with another as a line. There is no dual-axis chart on purpose — two measures of very different scale go in two charts. ALWAYS set `unit` (and `currency` when it is money): a currency drawn as a bare number is a figure the merchant will read as the wrong thing. ALWAYS set `source` with the tool you read, the window, and the row count, so the merchant can trace the number back. At most 6 series; if you have more, chart the ones that matter and say so. Write a one-line answer in prose as well — the chart supports the sentence, it does not replace it.","tags":["read"],"security":[{"bearerAuth":[]},{"oauth2":["read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"kind":{"type":"string","enum":["line","bar","area","stacked_bar","stacked_area","pie","donut","scatter","combo"],"description":"Chosen by the question the data answers, not by preference."},"data":{"type":"array","description":"The rows, each an object. Every yKey must be a NUMBER in every row it appears in; use null for a genuinely missing point rather than 0, which is a measurement.","items":{"type":"object"}},"xKey":{"type":"string","description":"The field on the x axis (or the slice name for pie/donut)."},"yKeys":{"type":"array","maxItems":6,"minItems":1,"items":{"type":"string"},"description":"The measures to plot. Six at most — the series palette has six slots and is never reused."},"labels":{"type":"object","description":"Display names keyed by raw field name, e.g. {\"rev\":\"Revenue\",\"d\":\"Day\"}."},"title":{"type":"string","description":"What the chart shows, as a short noun phrase."},"subtitle":{"type":"string","description":"The window, the filter, or the caveat. One line."},"unit":{"type":"string","enum":["number","currency","percent","duration_s","compact"],"description":"How to format values. Percentages are whole numbers (12.5 means 12.5%), not fractions."},"currency":{"type":"string","description":"ISO 4217, when unit is currency. e.g. EUR, GBP, USD."},"barKeys":{"type":"array","items":{"type":"string"},"description":"combo only: which series are bars."},"lineKeys":{"type":"array","items":{"type":"string"},"description":"combo only: which series are the line."},"reference":{"type":"object","description":"An optional marked rule: a target, a break-even, last period's average.","properties":{"y":{"type":"number"},"label":{"type":"string"}},"required":["y"]},"source":{"type":"object","description":"Where these numbers came from. Set it whenever the data came from a tool — it is what lets the merchant trace a figure back to the question that produced it.","properties":{"tool":{"type":"string"},"window":{"type":"string"},"rows":{"type":"integer"}}}},"required":["kind","data","xKey","yKeys"]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/show_metrics":{"post":{"operationId":"show_metrics","summary":"Draw up to four headline numbers as stat tiles instead of writing them in a sentence. Use this for the figures a merchan","description":"Draw up to four headline numbers as stat tiles instead of writing them in a sentence. Use this for the figures a merchant reads at a glance — revenue, orders, spend, ROAS, margin, open alerts — and then keep your prose for what they MEAN and what to do. Each item takes a label, a value, an optional unit (and 3-letter currency when it is money) and an optional delta {value, direction: up|down|flat, label} for the change against the comparison period. Never write the same numbers out again underneath.","tags":["read"],"security":[{"bearerAuth":[]},{"oauth2":["read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","minItems":1,"maxItems":4,"description":"The headline numbers, most important first.","items":{"type":"object","properties":{"label":{"type":"string","description":"e.g. \"Revenue, last 7 days\""},"value":{"description":"The number itself, or a pre-formatted string like \"0.57x\"."},"unit":{"type":"string","enum":["currency","percent","number","ratio","duration"]},"currency":{"type":"string","description":"ISO 4217, e.g. EUR. Only with unit=currency."},"delta":{"type":"object","properties":{"value":{"type":"number"},"direction":{"type":"string","enum":["up","down","flat"]},"label":{"type":"string","description":"e.g. \"vs prior week\""}},"required":["value","direction"]}},"required":["label","value"]}},"caption":{"type":"string","description":"One short line under the row: the period, or where the figures came from."}},"required":["items"]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/create_artifact":{"post":{"operationId":"create_artifact","summary":"Create a document the merchant keeps — a report, an analysis, a comparison table, a checklist — that opens beside the co","description":"Create a document the merchant keeps — a report, an analysis, a comparison table, a checklist — that opens beside the conversation and survives it. Use this instead of a long chat answer whenever the result is something they will come back to, share, or act on over days: a weekly performance review, a margin analysis, a launch plan, a list of products to fix. Do NOT use it for a direct answer to a direct question; that is a message. Write a specific title (a merchant will see it in a list beside twenty others), a one-line summary, and build the body out of blocks rather than one wall of prose. After creating it, say in one line what it contains and what you would do first.","tags":["read"],"security":[{"bearerAuth":[]},{"oauth2":["read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"kind":{"type":"string","enum":["report","table","chart","document","checklist"],"description":"What it is, for the list icon and filter."},"title":{"type":"string","maxLength":120,"description":"Specific enough to pick out of a list."},"summary":{"type":"string","maxLength":300,"description":"One line: what it shows and over what window."},"content":{"type":"array","description":"An ordered list of blocks. Each is one of: {\"type\":\"heading\",\"text\":\"…\",\"level\":2|3}; {\"type\":\"text\",\"text\":\"markdown\"}; {\"type\":\"kpi\",\"items\":[{\"label\":\"Revenue\",\"value\":12400,\"unit\":\"currency\",\"currency\":\"EUR\",\"delta\":{\"value\":8.2,\"period\":\"vs last week\",\"higher_is_better\":true},\"trend\":[1,2,3]}]} (1–4 items; a single item renders as the hero figure); {\"type\":\"table\",\"columns\":[{\"key\":\"sku\",\"label\":\"SKU\"},{\"key\":\"rev\",\"label\":\"Revenue\",\"unit\":\"currency\",\"currency\":\"EUR\"}],\"rows\":[{\"sku\":\"A1\",\"rev\":120}],\"caption\":\"…\",\"source\":{\"tool\":\"query_performance\",\"window\":\"last 30 days\",\"rows\":42}}; {\"type\":\"chart\",\"spec\":{…the render_chart spec…}}; {\"type\":\"callout\",\"tone\":\"info|warn|crit|ok\",\"title\":\"…\",\"text\":\"…\"}. Pick the form by the job: ONE current number is a kpi block, never a one-bar chart; a few headline numbers is one kpi block with several items; more than about seven rows of numbers that all matter is a table; a shape over time or across things is a chart. Always set unit (and currency for money), and fill in `source` on tables and charts so every figure can be traced back to the tool that produced it.","items":{"type":"object"}}},"required":["kind","title","content"]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/update_artifact":{"post":{"operationId":"update_artifact","summary":"Revise an artifact you or the merchant already has, by id. The previous version is kept and stays readable, so a change ","description":"Revise an artifact you or the merchant already has, by id. The previous version is kept and stays readable, so a change is never a loss. Use this whenever the merchant asks for a change to something you made — add a column, redo it for a different window, correct a figure — rather than creating a second near-identical document. `change_note` is required and is what the merchant reads in the version list: say what changed and why, not \"updated\".","tags":["read"],"security":[{"bearerAuth":[]},{"oauth2":["read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"artifact_id":{"type":"string","description":"The id returned when it was created."},"title":{"type":"string","maxLength":120},"summary":{"type":"string","maxLength":300},"content":{"type":"array","description":"An ordered list of blocks. Each is one of: {\"type\":\"heading\",\"text\":\"…\",\"level\":2|3}; {\"type\":\"text\",\"text\":\"markdown\"}; {\"type\":\"kpi\",\"items\":[{\"label\":\"Revenue\",\"value\":12400,\"unit\":\"currency\",\"currency\":\"EUR\",\"delta\":{\"value\":8.2,\"period\":\"vs last week\",\"higher_is_better\":true},\"trend\":[1,2,3]}]} (1–4 items; a single item renders as the hero figure); {\"type\":\"table\",\"columns\":[{\"key\":\"sku\",\"label\":\"SKU\"},{\"key\":\"rev\",\"label\":\"Revenue\",\"unit\":\"currency\",\"currency\":\"EUR\"}],\"rows\":[{\"sku\":\"A1\",\"rev\":120}],\"caption\":\"…\",\"source\":{\"tool\":\"query_performance\",\"window\":\"last 30 days\",\"rows\":42}}; {\"type\":\"chart\",\"spec\":{…the render_chart spec…}}; {\"type\":\"callout\",\"tone\":\"info|warn|crit|ok\",\"title\":\"…\",\"text\":\"…\"}. Pick the form by the job: ONE current number is a kpi block, never a one-bar chart; a few headline numbers is one kpi block with several items; more than about seven rows of numbers that all matter is a table; a shape over time or across things is a chart. Always set unit (and currency for money), and fill in `source` on tables and charts so every figure can be traced back to the tool that produced it.","items":{"type":"object"}},"change_note":{"type":"string","maxLength":300,"description":"What changed and why. Shown in the version history."}},"required":["artifact_id","change_note"]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/inspect_expense_import":{"post":{"operationId":"inspect_expense_import","summary":"Profile a spreadsheet the user uploaded to import as expenses, by its import_id. Returns the detected columns (which is ","description":"Profile a spreadsheet the user uploaded to import as expenses, by its import_id. Returns the detected columns (which is the store / amount / date / label), the distinct values of the store column tagged as matches-a-store / not-in-account / blank, a count of refund (negative) rows, the full list of the user's stores, each store's expense categories, and a 10-row sample. ALWAYS call this first when a sheet is attached, then CONFIRM with the user how to map each store value, the currency, and whether to skip refunds before calling propose_expense_import.","tags":["read"],"security":[{"bearerAuth":[]},{"oauth2":["costs:read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"import_id":{"type":"string","format":"uuid","description":"The staged expense_imports id."}},"required":["import_id"]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/propose_expense_import":{"post":{"operationId":"propose_expense_import","summary":"Build an approval card for importing a staged expense sheet, after you have CONFIRMED the plan with the user (store mapp","description":"Build an approval card for importing a staged expense sheet, after you have CONFIRMED the plan with the user (store mapping, currency, refund handling). Pass store_map: an object mapping each raw store-column value (lowercased, exactly as inspect_expense_import returned it) to either a store id, the literal \"__split__\" (shared cost, split equally across all stores), or \"__skip__\" (do not import). Set split_anchor_store_id to one store id when any value is \"__split__\". Returns a preview card the user approves; nothing is written until they do. Always call inspect_expense_import first and confirm the mapping before this.","tags":["propose_write"],"security":[{"bearerAuth":[]},{"oauth2":["costs:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"import_id":{"type":"string","format":"uuid"},"store_map":{"type":"object","description":"Lowercased store-column value → store id | \"__split__\" | \"__skip__\".","additionalProperties":{"type":"string"}},"split_anchor_store_id":{"type":"string","description":"Store id the __split__ rows anchor to."},"currency":{"type":"string","description":"ISO 4217, e.g. EUR. Defaults to the sheet/EUR."},"default_frequency":{"type":"string","enum":["one_off","daily","weekly","monthly","quarterly","yearly"],"description":"Default frequency for rows (use one_off for a dated ledger)."},"skip_refunds":{"type":"boolean","description":"Drop negative/refund rows. Default true."},"categorize":{"type":"boolean","description":"Auto-categorize rows. Default true."}},"required":["import_id","store_map"]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/inspect_cost_import":{"post":{"operationId":"inspect_cost_import","summary":"Resolve a supplier cost sheet the user uploaded, by its import_id. Returns a RESOLVED MAPPING (which column identifies t","description":"Resolve a supplier cost sheet the user uploaded, by its import_id. Returns a RESOLVED MAPPING (which column identifies the product, which column holds the cost, the currency), how many rows actually match this store's catalogue, rows worth flagging, and — the important field — `questions`: the ONLY things the sheet could not answer by itself. ALWAYS call this first. If `ready_to_propose` is true, ask NOTHING and call propose_cost_import straight away. Otherwise ask exactly the questions listed, one at a time, with ask_user_question, passing each option's label, description AND value through unchanged — the value is the token propose_cost_import needs and it comes back on the user's answer. Never invent a question the sheet already answers, and never retype a value.","tags":["read"],"security":[{"bearerAuth":[]},{"oauth2":["costs:read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"import_id":{"type":"string","format":"uuid","description":"The staged cost_imports id."}},"required":["import_id"]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/propose_cost_import":{"post":{"operationId":"propose_cost_import","summary":"Build an approval card to import product costs from a staged sheet. Call inspect_cost_import first: if it returned ready","description":"Build an approval card to import product costs from a staged sheet. Call inspect_cost_import first: if it returned ready_to_propose=true, call this with ONLY import_id — the mapping is already settled and passing columns can only make it worse. Otherwise pass just the answers to the questions inspect_cost_import listed (identifier_column, cost_column and/or currency). Returns a preview card the user approves; nothing is written until they do.","tags":["propose_write"],"security":[{"bearerAuth":[]},{"oauth2":["costs:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"import_id":{"type":"string","format":"uuid"},"identifier_column":{"type":"string","description":"Only when inspect_cost_import asked which column identifies the product."},"cost_column":{"type":"string","description":"Only when inspect_cost_import asked which column holds the unit cost."},"currency":{"type":"string","description":"Only when inspect_cost_import asked. Pass the chosen option's value VERBATIM — either an ISO 4217 code, or the \"convert:<ISO>\" form, which converts the sheet's stated currency into <ISO> at the platform rate."},"include_flagged_rows":{"type":"boolean","description":"Import rows the sheet itself flagged (a note saying CHECK / wrong / verify). Default false — they are left out and counted. Only pass true if the user, having been told what the note says, asks for them."},"title_column":{"type":"string","description":"Manual override for the product title column."},"sku_column":{"type":"string","description":"Manual override for the SKU column."}},"required":["import_id"]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/cross_store_link_status":{"post":{"operationId":"cross_store_link_status","summary":"Check how many products of the operator's OTHER stores are linked to a master store (the store all their products derive","description":"Check how many products of the operator's OTHER stores are linked to a master store (the store all their products derive from), and preview which still-unlinked products would match which master product (by embedding, with a confidence score). Defaults the master to the current store. Read-only — call this to answer \"are all my products linked?\" or before proposing to link them.","tags":["read"],"security":[{"bearerAuth":[]},{"oauth2":["admin:read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"master_store_id":{"type":"string","format":"uuid","description":"Master store id; defaults to the current store."}},"required":[]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/propose_cross_store_link":{"post":{"operationId":"propose_cross_store_link","summary":"Build an approval card to LINK every still-unlinked product of the operator's other stores to the master store (the curr","description":"Build an approval card to LINK every still-unlinked product of the operator's other stores to the master store (the current store by default), matched by embedding. On approval it creates dry-run link groups (nothing propagates until cross-store sync is enabled). Call cross_store_link_status first to show the coverage. Returns a preview card; nothing is written until the user approves.","tags":["propose_write"],"security":[{"bearerAuth":[]},{"oauth2":["admin:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"master_store_id":{"type":"string","format":"uuid","description":"Master store id; defaults to the current store."}},"required":[]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/ads_run_gaql":{"post":{"operationId":"ads_run_gaql","summary":"Run an arbitrary read-only Google Ads Query Language (GAQL) query against the store's Google Ads account and get rows ba","description":"Run an arbitrary read-only Google Ads Query Language (GAQL) query against the store's Google Ads account and get rows back. Covers the whole reporting surface: campaigns, ad_group, keyword_view, search_term_view, ad_group_ad, asset, geographic_view, campaign_criterion, change_event, etc. Example: \"SELECT campaign.name, metrics.clicks, metrics.cost_micros FROM campaign WHERE segments.date DURING LAST_7_DAYS ORDER BY metrics.clicks DESC\".","tags":["read"],"security":[{"bearerAuth":[]},{"oauth2":["ads:read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"query":{"type":"string","maxLength":8000,"description":"A GAQL SELECT query."},"limit":{"type":"integer","minimum":1,"maximum":2000,"description":"Max rows returned (default 500)."}},"required":["query"]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/ads_list_accessible_customers":{"post":{"operationId":"ads_list_accessible_customers","summary":"List the Google Ads customer accounts this store’s credential can access (id, name, currency, time zone, manager flag).","description":"List the Google Ads customer accounts this store’s credential can access (id, name, currency, time zone, manager flag).","tags":["read"],"security":[{"bearerAuth":[]},{"oauth2":["ads:read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{},"required":[]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/ads_set_campaign_status":{"post":{"operationId":"ads_set_campaign_status","summary":"Pause or resume a Google Ads campaign. Applies via the connected store — creates an approval card unless auto-apply is e","description":"Pause or resume a Google Ads campaign. Applies via the connected store — creates an approval card unless auto-apply is enabled for this connection. Use get_ad_campaigns first to get the internal campaign_id.","tags":["propose_write"],"security":[{"bearerAuth":[]},{"oauth2":["ads:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"campaign_id":{"type":"string","description":"Magistry internal campaign id (from get_ad_campaigns)."},"status":{"type":"string","enum":["paused","active"],"description":"Target status."}},"required":["campaign_id","status"]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/ads_set_campaign_budget":{"post":{"operationId":"ads_set_campaign_budget","summary":"Set a Google Ads campaign's daily budget (in the account currency, major units). Enforced against the store safety caps.","description":"Set a Google Ads campaign's daily budget (in the account currency, major units). Enforced against the store safety caps. Approval/auto-apply gated.","tags":["propose_write"],"security":[{"bearerAuth":[]},{"oauth2":["ads:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"campaign_id":{"type":"string","description":"Magistry internal campaign id."},"daily_budget":{"type":"number","minimum":0,"maximum":100000,"description":"New daily budget (major units)."}},"required":["campaign_id","daily_budget"]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/ads_add_negative_keywords":{"post":{"operationId":"ads_add_negative_keywords","summary":"Add negative keywords to ad groups / campaigns in bulk (waste-cutting). Approval/auto-apply gated.","description":"Add negative keywords to ad groups / campaigns in bulk (waste-cutting). Approval/auto-apply gated.","tags":["propose_write"],"security":[{"bearerAuth":[]},{"oauth2":["ads:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","maxItems":200,"description":"Negative keywords to add.","items":{"type":"object","properties":{"query":{"type":"string"},"ad_group_id":{"type":"string"},"campaign_id":{"type":"string"},"match_type":{"type":"string","enum":["negative_exact","negative_phrase","negative_broad"]},"platform":{"type":"string","enum":["google","meta","tiktok","bing","pinterest"]}},"required":["query","ad_group_id","campaign_id","match_type"]}}},"required":["items"]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/ads_create_campaign":{"post":{"operationId":"ads_create_campaign","summary":"Create a new Google Ads campaign (created PAUSED for safety; activate it with ads_set_campaign_status). For search/shopp","description":"Create a new Google Ads campaign (created PAUSED for safety; activate it with ads_set_campaign_status). For search/shopping, an ad group and a first RSA are created too (needs ad_group_name, ≥3 headlines, ≥2 descriptions, final_url). Must be in the store allowed_actions. Approval/auto-apply gated.","tags":["propose_write"],"security":[{"bearerAuth":[]},{"oauth2":["ads:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"campaign_name":{"type":"string","maxLength":120},"channel_type":{"type":"string","enum":["search","shopping","display","performance_max","video"]},"daily_budget":{"type":"number","minimum":0,"maximum":100000},"bidding_strategy":{"type":"string","description":"e.g. MAXIMIZE_CONVERSIONS, TARGET_ROAS, TARGET_CPA, MANUAL_CPC."},"target_roas":{"type":"number","description":"Target ROAS (for TARGET_ROAS)."},"target_cpa":{"type":"number","description":"Target CPA (for TARGET_CPA)."},"start_date":{"type":"string","description":"ISO date (optional)."},"ad_group_name":{"type":"string","description":"Required for search/shopping."},"ad_group_cpc_bid":{"type":"number"},"headlines":{"type":"array","items":{"type":"string"},"description":"≥3 for search/shopping RSA."},"descriptions":{"type":"array","items":{"type":"string"},"description":"≥2 for search/shopping RSA."},"final_url":{"type":"string","description":"Landing page URL (required for RSA)."}},"required":["campaign_name","channel_type","daily_budget","bidding_strategy"]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/meta_run_insights":{"post":{"operationId":"meta_run_insights","summary":"Run a Meta Ads Insights query against the store's ad account. Pick a level (account/campaign/adset/ad), a date range (si","description":"Run a Meta Ads Insights query against the store's ad account. Pick a level (account/campaign/adset/ad), a date range (since+until ISO dates, or a date_preset like last_7d/last_30d), optional breakdowns (e.g. \"publisher_platform\", \"age,gender\", \"country\") and optional explicit fields. Returns raw insight rows incl. spend, impressions, clicks, ctr, cpm, frequency, reach and the purchase actions/action_values.","tags":["read"],"security":[{"bearerAuth":[]},{"oauth2":["ads:read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"level":{"type":"string","enum":["account","campaign","adset","ad"],"description":"Aggregation level (default campaign)."},"since":{"type":"string","description":"ISO start date (with until)."},"until":{"type":"string","description":"ISO end date (with since)."},"date_preset":{"type":"string","description":"Meta date preset (default last_30d) when since/until absent."},"time_increment":{"type":"integer","description":"1 for daily rows."},"breakdowns":{"type":"string","description":"Comma-separated Meta breakdowns."},"fields":{"type":"string","description":"Comma-separated insight fields (sane default)."},"limit":{"type":"integer","minimum":1,"maximum":500,"description":"Max rows (default 200)."}},"required":[]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/meta_get_ad_entities":{"post":{"operationId":"meta_get_ad_entities","summary":"List raw Meta ad objects (campaigns, adsets or ads) straight from the Graph API, incl. objective, budgets, bid strategy,","description":"List raw Meta ad objects (campaigns, adsets or ads) straight from the Graph API, incl. objective, budgets, bid strategy, Advantage+ flag, optimization goal and targeting. Use for structure the synced tables do not carry.","tags":["read"],"security":[{"bearerAuth":[]},{"oauth2":["ads:read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"kind":{"type":"string","enum":["campaigns","adsets","ads"],"description":"Entity kind (default campaigns)."},"fields":{"type":"string","description":"Comma-separated Graph fields override."},"limit":{"type":"integer","minimum":1,"maximum":500}},"required":[]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/meta_get_audiences":{"post":{"operationId":"meta_get_audiences","summary":"List the ad account's Custom Audiences (id, name, subtype, approximate size bounds, delivery/operation status). The Magi","description":"List the ad account's Custom Audiences (id, name, subtype, approximate size bounds, delivery/operation status). The Magistry-managed customer-list audiences carry the \"Magistry · \" name prefix.","tags":["read"],"security":[{"bearerAuth":[]},{"oauth2":["ads:read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{},"required":[]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/meta_create_campaign":{"post":{"operationId":"meta_create_campaign","summary":"Create a new Meta campaign (created PAUSED for safety; activate with ads_set_campaign_status). channel_type \"advantage_p","description":"Create a new Meta campaign (created PAUSED for safety; activate with ads_set_campaign_status). channel_type \"advantage_plus\" creates an Advantage+ Shopping campaign (needs pixel + catalog). Optionally creates a first ad set (ad_group_name — conversion-optimized when a pixel is connected, targeting the store market) and a first link ad (needs ≥1 headline, ≥1 description, final_url, and a connected Facebook Page). Approval/auto-apply gated.","tags":["propose_write"],"security":[{"bearerAuth":[]},{"oauth2":["ads:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"campaign_name":{"type":"string","maxLength":120},"channel_type":{"type":"string","enum":["advantage_plus","sales","traffic","awareness","leads"]},"daily_budget":{"type":"number","minimum":0,"maximum":100000,"description":"Campaign daily budget (CBO), account currency."},"bidding_strategy":{"type":"string","description":"MAXIMIZE_CONVERSIONS (default) / MAXIMIZE_CONVERSION_VALUE / LOWEST_COST."},"ad_group_name":{"type":"string","description":"Create a first ad set with this name."},"headlines":{"type":"array","items":{"type":"string"},"description":"headlines[0] → ad headline."},"descriptions":{"type":"array","items":{"type":"string"},"description":"descriptions[0] → primary text, [1] → link description."},"final_url":{"type":"string","description":"Landing page URL for the first ad."}},"required":["campaign_name","channel_type","daily_budget"]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/meta_sync_audiences":{"post":{"operationId":"meta_sync_audiences","summary":"Rebuild and upload the Magistry-managed Meta Custom Audiences from the store's order history: all customers, buyers 180d","description":"Rebuild and upload the Magistry-managed Meta Custom Audiences from the store's order history: all customers, buyers 180d, recent buyers 30d (exclusion) and high-value (top-quartile lifetime spend, the best lookalike origin). Hashed + consent-gated (only customers without a marketing opt-out). Approval/auto-apply gated — uploading customer data to Meta is a real data share.","tags":["propose_write"],"security":[{"bearerAuth":[]},{"oauth2":["ads:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{},"required":[]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/shopify_get_shop_profile":{"post":{"operationId":"shopify_get_shop_profile","summary":"Fetch the connected Shopify shop profile (name, domain, currency, plan, timezone).","description":"Fetch the connected Shopify shop profile (name, domain, currency, plan, timezone).","tags":["read"],"security":[{"bearerAuth":[]},{"oauth2":["catalog:read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{},"required":[]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/shopify_list_markets":{"post":{"operationId":"shopify_list_markets","summary":"List the store’s Shopify Markets (regions, currencies, enabled state).","description":"List the store’s Shopify Markets (regions, currencies, enabled state).","tags":["read"],"security":[{"bearerAuth":[]},{"oauth2":["catalog:read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{},"required":[]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/shopify_graphql_query":{"post":{"operationId":"shopify_graphql_query","summary":"Run an arbitrary READ-ONLY Shopify Admin GraphQL query and get the raw data back — the Shopify analog of GAQL. Full Admi","description":"Run an arbitrary READ-ONLY Shopify Admin GraphQL query and get the raw data back — the Shopify analog of GAQL. Full Admin API read surface: orders, customers, products, variants, inventory, fulfillments, metafields, discounts, draft orders, and more. Mutations are rejected (use the typed write tools). Example: \"{ orders(first: 5, query: \\\"financial_status:paid\\\") { edges { node { id name totalPriceSet { shopMoney { amount } } } } } }\".","tags":["read"],"security":[{"bearerAuth":[]},{"oauth2":["admin:read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"query":{"type":"string","maxLength":20000,"description":"A read-only Shopify Admin GraphQL query."},"variables":{"type":"object","description":"Optional GraphQL variables."}},"required":["query"]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/shopify_set_product_status":{"post":{"operationId":"shopify_set_product_status","summary":"Set a product’s Shopify status (active / draft / archived). Approval/auto-apply gated.","description":"Set a product’s Shopify status (active / draft / archived). Approval/auto-apply gated.","tags":["propose_write"],"security":[{"bearerAuth":[]},{"oauth2":["catalog:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"shopify_product_id":{"type":"integer","description":"Numeric Shopify product id."},"status":{"type":"string","enum":["active","draft","archived"]}},"required":["shopify_product_id","status"]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/shopify_update_variant_price":{"post":{"operationId":"shopify_update_variant_price","summary":"Set a variant’s price (optionally a compare-at price for a strikethrough). Approval/auto-apply gated.","description":"Set a variant’s price (optionally a compare-at price for a strikethrough). Approval/auto-apply gated.","tags":["propose_write"],"security":[{"bearerAuth":[]},{"oauth2":["catalog:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"shopify_variant_id":{"type":"integer","description":"Numeric Shopify variant id."},"new_price":{"type":"number","minimum":0},"compare_at_price":{"type":"number","minimum":0,"description":"Optional original price to show struck-through."}},"required":["shopify_variant_id","new_price"]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/shopify_update_inventory":{"post":{"operationId":"shopify_update_inventory","summary":"Set available inventory for a product’s variants at the primary location. `option_qty` maps a variant’s option values (j","description":"Set available inventory for a product’s variants at the primary location. `option_qty` maps a variant’s option values (joined by \"/\", e.g. \"Brown/10\") to a quantity. Approval/auto-apply gated.","tags":["propose_write"],"security":[{"bearerAuth":[]},{"oauth2":["catalog:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"shopify_product_id":{"type":"integer"},"option_qty":{"type":"object","description":"Map of \"Opt1/Opt2\" → quantity. Single-option variants use just the value.","additionalProperties":{"type":"integer"}}},"required":["shopify_product_id","option_qty"]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/shopify_update_tags":{"post":{"operationId":"shopify_update_tags","summary":"Replace a product’s full tag list. Approval/auto-apply gated.","description":"Replace a product’s full tag list. Approval/auto-apply gated.","tags":["propose_write"],"security":[{"bearerAuth":[]},{"oauth2":["catalog:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"shopify_product_id":{"type":"integer"},"tags":{"type":"array","items":{"type":"string"},"maxItems":250,"description":"The complete replacement tag set."}},"required":["shopify_product_id","tags"]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/shopify_set_metafield":{"post":{"operationId":"shopify_set_metafield","summary":"Upsert a single metafield on any Shopify owner (Product, Variant, Order, …). `owner_gid` is the full gid, e.g. gid://sho","description":"Upsert a single metafield on any Shopify owner (Product, Variant, Order, …). `owner_gid` is the full gid, e.g. gid://shopify/Product/123. Approval/auto-apply gated.","tags":["propose_write"],"security":[{"bearerAuth":[]},{"oauth2":["catalog:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"owner_gid":{"type":"string","description":"Full Shopify GID of the owner resource."},"namespace":{"type":"string","maxLength":40},"key":{"type":"string","maxLength":64},"value":{"type":"string"},"type":{"type":"string","description":"Metafield type (default single_line_text_field)."}},"required":["owner_gid","namespace","key","value"]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/shopify_update_content":{"post":{"operationId":"shopify_update_content","summary":"Update a product’s content: title, body HTML, SEO title/description, and/or tags. Only the fields you pass are changed. ","description":"Update a product’s content: title, body HTML, SEO title/description, and/or tags. Only the fields you pass are changed. Approval/auto-apply gated.","tags":["propose_write"],"security":[{"bearerAuth":[]},{"oauth2":["catalog:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"shopify_product_id":{"type":"integer"},"title":{"type":"string","maxLength":255},"body_html":{"type":"string","description":"Product description HTML."},"seo_title":{"type":"string","maxLength":255},"seo_description":{"type":"string","maxLength":500},"tags":{"type":"array","items":{"type":"string"},"maxItems":250,"description":"Full replacement tag set."}},"required":["shopify_product_id"]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/shopify_update_variant_cost":{"post":{"operationId":"shopify_update_variant_cost","summary":"Set a variant's cost-per-item (unit cost / COGS). Approval/auto-apply gated.","description":"Set a variant's cost-per-item (unit cost / COGS). Approval/auto-apply gated.","tags":["propose_write"],"security":[{"bearerAuth":[]},{"oauth2":["costs:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"shopify_variant_id":{"type":"integer"},"cost":{"type":"number","minimum":0,"description":"Cost per item in the store currency."}},"required":["shopify_variant_id","cost"]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/shopify_create_discount_code":{"post":{"operationId":"shopify_create_discount_code","summary":"Create a basic Shopify discount code. `kind` percentage (value 10 = 10%) or fixed_amount (value in store currency). Opti","description":"Create a basic Shopify discount code. `kind` percentage (value 10 = 10%) or fixed_amount (value in store currency). Optionally lock to one customer_gid and cap usage. Approval/auto-apply gated.","tags":["propose_write"],"security":[{"bearerAuth":[]},{"oauth2":["promotions:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"code":{"type":"string","maxLength":60},"kind":{"type":"string","enum":["percentage","fixed_amount"]},"value":{"type":"number","description":"10 = 10% (percentage) or 10 currency units (fixed_amount)."},"title":{"type":"string","maxLength":120},"customer_gid":{"type":"string","description":"gid://shopify/Customer/123 to restrict to one customer."},"usage_limit":{"type":"integer","minimum":1,"description":"Default 1 (single-use)."},"minimum_subtotal":{"type":"number","description":"Optional minimum order subtotal."}},"required":["code","kind","value"]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/ga4_get_admin_config":{"post":{"operationId":"ga4_get_admin_config","summary":"GA4 property health: data retention, key events, Google Ads links, Google Signals state, and the reporting attribution m","description":"GA4 property health: data retention, key events, Google Ads links, Google Signals state, and the reporting attribution model.","tags":["read"],"security":[{"bearerAuth":[]},{"oauth2":["analytics:read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{},"required":[]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/ga4_list_audiences":{"post":{"operationId":"ga4_list_audiences","summary":"List the GA4 property’s audiences (name, membership window, description).","description":"List the GA4 property’s audiences (name, membership window, description).","tags":["read"],"security":[{"bearerAuth":[]},{"oauth2":["analytics:read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{},"required":[]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/ga4_list_data_streams":{"post":{"operationId":"ga4_list_data_streams","summary":"List the GA4 data streams (web/app), including each web stream’s measurement id.","description":"List the GA4 data streams (web/app), including each web stream’s measurement id.","tags":["read"],"security":[{"bearerAuth":[]},{"oauth2":["analytics:read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{},"required":[]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/ga4_run_report":{"post":{"operationId":"ga4_run_report","summary":"Run a compact GA4 report over the last N days. Provide GA4 API dimension and metric names (e.g. dimensions [\"sessionDefa","description":"Run a compact GA4 report over the last N days. Provide GA4 API dimension and metric names (e.g. dimensions [\"sessionDefaultChannelGroup\"], metrics [\"sessions\",\"totalRevenue\"]).","tags":["read"],"security":[{"bearerAuth":[]},{"oauth2":["analytics:read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"dimensions":{"type":"array","items":{"type":"string"},"maxItems":9},"metrics":{"type":"array","items":{"type":"string"},"maxItems":10},"days":{"type":"integer","minimum":1,"maximum":365,"description":"Lookback window (default 30)."},"limit":{"type":"integer","minimum":1,"maximum":250,"description":"Max rows (default 50)."}},"required":["dimensions","metrics"]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/ga4_channel_performance":{"post":{"operationId":"ga4_channel_performance","summary":"Sessions / transactions / revenue / engagement per default channel group.","description":"Sessions / transactions / revenue / engagement per default channel group.","tags":["read"],"security":[{"bearerAuth":[]},{"oauth2":["analytics:read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"days":{"type":"integer","minimum":1,"maximum":365,"description":"Default 30."}},"required":[]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/ga4_buyer_demographics":{"post":{"operationId":"ga4_buyer_demographics","summary":"Gender split of sessions vs purchase revenue (needs Google Signals on).","description":"Gender split of sessions vs purchase revenue (needs Google Signals on).","tags":["read"],"security":[{"bearerAuth":[]},{"oauth2":["analytics:read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"days":{"type":"integer","minimum":1,"maximum":365,"description":"Default 90."}},"required":[]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/ga4_set_data_retention":{"post":{"operationId":"ga4_set_data_retention","summary":"Set GA4 event data retention. The common fix is raising the silent default TWO_MONTHS to FOURTEEN_MONTHS+ so exploration","description":"Set GA4 event data retention. The common fix is raising the silent default TWO_MONTHS to FOURTEEN_MONTHS+ so explorations/audiences keep history. Approval/auto-apply gated.","tags":["propose_write"],"security":[{"bearerAuth":[]},{"oauth2":["analytics:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"months":{"type":"string","enum":["TWO_MONTHS","FOURTEEN_MONTHS","TWENTY_SIX_MONTHS","THIRTY_EIGHT_MONTHS","FIFTY_MONTHS"]}},"required":["months"]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/ga4_create_audience":{"post":{"operationId":"ga4_create_audience","summary":"Create a GA4 audience: users who fired `include_event` within the membership window (optionally excluding those who fire","description":"Create a GA4 audience: users who fired `include_event` within the membership window (optionally excluding those who fired `exclude_event`). Flows to linked Google Ads accounts. Create-only. Approval/auto-apply gated.","tags":["propose_write"],"security":[{"bearerAuth":[]},{"oauth2":["analytics:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"display_name":{"type":"string","maxLength":100},"description":{"type":"string","maxLength":100},"include_event":{"type":"string","description":"GA4 event name that qualifies a user."},"membership_days":{"type":"integer","minimum":1,"maximum":540},"exclude_event":{"type":"string","description":"Optional event that temporarily excludes a user."}},"required":["display_name","include_event","membership_days"]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/merchant_get_account_issues":{"post":{"operationId":"merchant_get_account_issues","summary":"List account-level Merchant Center issues (unclaimed website, missing business info, shipping/tax, policy enforcement) —","description":"List account-level Merchant Center issues (unclaimed website, missing business info, shipping/tax, policy enforcement) — the ones that throttle the whole feed.","tags":["read"],"security":[{"bearerAuth":[]},{"oauth2":["merchant:read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{},"required":[]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/merchant_get_product_views":{"post":{"operationId":"merchant_get_product_views","summary":"List product offers with their Merchant Center approval status per reporting context and country (eligible / disapproved","description":"List product offers with their Merchant Center approval status per reporting context and country (eligible / disapproved / pending).","tags":["read"],"security":[{"bearerAuth":[]},{"oauth2":["merchant:read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"limit":{"type":"integer","minimum":1,"maximum":1000,"description":"Max offers (default 200)."}},"required":[]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/merchant_run_report":{"post":{"operationId":"merchant_run_report","summary":"Run an arbitrary read-only Merchant Center reports query (Merchant Query Language) — the Merchant analog of GAQL. Tables","description":"Run an arbitrary read-only Merchant Center reports query (Merchant Query Language) — the Merchant analog of GAQL. Tables: product_view, product_performance_view, price_competitiveness_product_view, best_sellers_product_cluster_view, competitive_visibility_competitor_view, etc. Example: \"SELECT offer_id, clicks, impressions FROM product_performance_view WHERE date BETWEEN '2026-06-01' AND '2026-06-30' ORDER BY clicks DESC\".","tags":["read"],"security":[{"bearerAuth":[]},{"oauth2":["merchant:read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"query":{"type":"string","maxLength":8000,"description":"A Merchant Query Language SELECT query."},"limit":{"type":"integer","minimum":1,"maximum":2000,"description":"Max rows (default 500)."}},"required":["query"]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/merchant_upsert_supplemental_labels":{"post":{"operationId":"merchant_upsert_supplemental_labels","summary":"Set custom labels (customLabel0..4) on one offer via Magistry’s supplemental data source, without touching the primary f","description":"Set custom labels (customLabel0..4) on one offer via Magistry’s supplemental data source, without touching the primary feed. `labels` maps slot (\"0\"..\"4\") to value. Approval/auto-apply gated.","tags":["propose_write"],"security":[{"bearerAuth":[]},{"oauth2":["merchant:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"offer_id":{"type":"string","description":"Merchant offer id."},"content_language":{"type":"string","description":"e.g. \"nl\", \"en\"."},"feed_label":{"type":"string","description":"Feed label / target country code, e.g. \"NL\"."},"labels":{"type":"object","description":"Map of custom-label slot \"0\"..\"4\" → value.","additionalProperties":{"type":"string","maxLength":100}}},"required":["offer_id","content_language","feed_label","labels"]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/merchant_delete_supplemental_labels":{"post":{"operationId":"merchant_delete_supplemental_labels","summary":"Remove Magistry’s supplemental custom-label input for one offer (the offer reverts to exactly what the primary feed supp","description":"Remove Magistry’s supplemental custom-label input for one offer (the offer reverts to exactly what the primary feed supplies). Approval/auto-apply gated.","tags":["propose_write"],"security":[{"bearerAuth":[]},{"oauth2":["merchant:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"offer_id":{"type":"string"},"content_language":{"type":"string"},"feed_label":{"type":"string"}},"required":["offer_id","content_language","feed_label"]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/shopify_create_refund":{"post":{"operationId":"shopify_create_refund","summary":"Issue a partial or full refund on an order against its original payment transaction. Irreversible. Approval/auto-apply g","description":"Issue a partial or full refund on an order against its original payment transaction. Irreversible. Approval/auto-apply gated.","tags":["propose_write"],"security":[{"bearerAuth":[]},{"oauth2":["orders:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"shopify_order_id":{"type":"integer","description":"Numeric Shopify order id."},"amount":{"type":"number","minimum":0,"description":"Refund amount in the store currency."},"currency":{"type":"string","description":"ISO currency (default EUR)."},"reason":{"type":"string","maxLength":200},"notify_customer":{"type":"boolean","description":"Email the customer (default true)."}},"required":["shopify_order_id","amount"]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/shopify_cancel_order":{"post":{"operationId":"shopify_cancel_order","summary":"Cancel an order (pre-fulfilment; Shopify enforces the guard). Optionally refund and restock. Irreversible. Approval/auto","description":"Cancel an order (pre-fulfilment; Shopify enforces the guard). Optionally refund and restock. Irreversible. Approval/auto-apply gated.","tags":["propose_write"],"security":[{"bearerAuth":[]},{"oauth2":["orders:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"shopify_order_id":{"type":"integer"},"reason":{"type":"string","enum":["CUSTOMER","FRAUD","INVENTORY","DECLINED","OTHER"]},"notify_customer":{"type":"boolean"},"refund":{"type":"boolean","description":"Refund the payment (default true)."},"restock":{"type":"boolean","description":"Restock the items (default true)."}},"required":["shopify_order_id"]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/shopify_update_shipping_address":{"post":{"operationId":"shopify_update_shipping_address","summary":"Update an order’s shipping address (pre-fulfilment). Approval/auto-apply gated.","description":"Update an order’s shipping address (pre-fulfilment). Approval/auto-apply gated.","tags":["propose_write"],"security":[{"bearerAuth":[]},{"oauth2":["orders:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"shopify_order_id":{"type":"integer"},"address1":{"type":"string"},"address2":{"type":"string"},"city":{"type":"string"},"province":{"type":"string","description":"State/province."},"country":{"type":"string"},"zip_code":{"type":"string"},"name":{"type":"string"},"phone":{"type":"string"}},"required":["shopify_order_id","address1","city","country","zip_code"]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/shopify_create_gift_card":{"post":{"operationId":"shopify_create_gift_card","summary":"Issue a gift card (store credit), optionally emailed to the customer of a named order. Capped at the store's CS max_gift","description":"Issue a gift card (store credit), optionally emailed to the customer of a named order. Capped at the store's CS max_gift_card_amount; above it the result is needs_owner and nothing is issued. Approval/auto-apply gated.","tags":["propose_write"],"security":[{"bearerAuth":[]},{"oauth2":["promotions:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"amount":{"type":"number","minimum":0},"currency":{"type":"string","description":"ISO currency (default EUR)."},"shopify_order_id":{"type":"integer","description":"Numeric Shopify order id. Required when recipient_email is set: the card is e-mailed to that order's customer, whatever address was given."},"recipient_email":{"type":"string","description":"Optional. Only honoured together with shopify_order_id, and then bound to that order's customer. Omit to issue unassigned store credit."},"note":{"type":"string","maxLength":200}},"required":["amount"]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/cs_send_reply":{"post":{"operationId":"cs_send_reply","summary":"Send a reply on a customer-service thread. `reply_to_message_id` is the inbound message being answered (from get_cs_thre","description":"Send a reply on a customer-service thread. `reply_to_message_id` is the inbound message being answered (from get_cs_thread). Emails the customer, so it is approval/auto-apply gated, and the body is scored by the CS desk's own reply judge first: a draft the judge will not send comes back needs_owner with the judge's rationale. Read the rationale, fix the body and call again — do not resend the same text.","tags":["propose_write"],"security":[{"bearerAuth":[]},{"oauth2":["cs:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"thread_id":{"type":"string","format":"uuid"},"reply_to_message_id":{"type":"string","format":"uuid","description":"The inbound message to reply to."},"body_text":{"type":"string","maxLength":20000,"description":"Reply body (plain text)."},"append_signature":{"type":"boolean","description":"Append the mailbox signature (default true)."}},"required":["thread_id","reply_to_message_id","body_text"]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/cs_triage_thread":{"post":{"operationId":"cs_triage_thread","summary":"Apply a reversible inbox action to one or more CS threads (mark read/unread, star/unstar, move to spam/trash/archive/inb","description":"Apply a reversible inbox action to one or more CS threads (mark read/unread, star/unstar, move to spam/trash/archive/inbox), mirrored to the mailbox.","tags":["auto_write"],"security":[{"bearerAuth":[]},{"oauth2":["cs:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"thread_ids":{"type":"array","items":{"type":"string","format":"uuid"},"maxItems":100},"action":{"type":"string","enum":["mark_read","mark_unread","star","unstar","move_spam","move_trash","move_archive","move_inbox"]}},"required":["thread_ids","action"]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/cs_copilot_ask":{"post":{"operationId":"cs_copilot_ask","summary":"Ask the CS Copilot about one thread — why the agent decided what it did, the customer history, which policy applied, or ","description":"Ask the CS Copilot about one thread — why the agent decided what it did, the customer history, which policy applied, or for a suggested/softened reply. Read-only: it answers from the agent's signal bundle + recorded decisions and never sends or changes anything.","tags":["read"],"security":[{"bearerAuth":[]},{"oauth2":["cs:read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"thread_id":{"type":"string","format":"uuid"},"question":{"type":"string","maxLength":4000,"description":"What to ask about this thread."}},"required":["thread_id","question"]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/get_store_settings":{"post":{"operationId":"get_store_settings","summary":"Read the store’s configuration: global kill switch, automation toggles, and the deep agents’ (Catalog / Campaign / CS) e","description":"Read the store’s configuration: global kill switch, automation toggles, and the deep agents’ (Catalog / Campaign / CS) execution modes.","tags":["read"],"security":[{"bearerAuth":[]},{"oauth2":["admin:read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{},"required":[]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/set_kill_switch":{"post":{"operationId":"set_kill_switch","summary":"Set the store’s global kill switch. When ON, no autonomous job acts for this store. Immediate and reversible.","description":"Set the store’s global kill switch. When ON, no autonomous job acts for this store. Immediate and reversible.","tags":["auto_write"],"security":[{"bearerAuth":[]},{"oauth2":["admin:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"enabled":{"type":"boolean","description":"true = halt all autonomy."}},"required":["enabled"]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/set_automation_enabled":{"post":{"operationId":"set_automation_enabled","summary":"Enable or disable a named automation feature (as listed by get_store_settings). Preserves the feature’s existing caps. I","description":"Enable or disable a named automation feature (as listed by get_store_settings). Preserves the feature’s existing caps. Immediate and reversible.","tags":["auto_write"],"security":[{"bearerAuth":[]},{"oauth2":["admin:write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"feature":{"type":"string","description":"Automation feature key (see get_store_settings.automations)."},"enabled":{"type":"boolean"}},"required":["feature","enabled"]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/get_brain_thinking":{"post":{"operationId":"get_brain_thinking","summary":"How the Brain is REASONING and when it will next act — the detail behind get_marketing_brain. Returns its current strate","description":"How the Brain is REASONING and when it will next act — the detail behind get_marketing_brain. Returns its current strategy (thesis, plan, timeline, expected outcomes, risks, open questions, target markets, confidence), the standing instructions the merchant has given it, its cycle schedule (last cycle, cadence, when the next one is due, and whether a pending critical signal will preempt it), why the last cycle held off if it did, what is queued for it to read next, and the proposals it closed with the reason each was closed. Use whenever the merchant asks why the Brain decided something, what it is planning, what it is waiting for, when it will act, what it has been told to do or not do, or why it did nothing.","tags":["read"],"security":[{"bearerAuth":[]},{"oauth2":["read"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{},"required":[]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/tools/tell_brain":{"post":{"operationId":"tell_brain","summary":"Pass the merchant's direction to the Brain. Use `add_instruction` when they state a standing goal or guardrail (\"stop di","description":"Pass the merchant's direction to the Brain. Use `add_instruction` when they state a standing goal or guardrail (\"stop discounting\", \"focus on the winter range\", \"never advertise brand X\", \"keep spend under 40 a day\") — the Brain reads these on every cycle and treats a guardrail as a hard constraint that overrides its own judgement. Use `remove_instruction` with the EXACT wording of an existing one when they retract it; call get_brain_thinking first and read the list back to them. Use `note` for a one-off fact the next cycle should know (\"we are out of stock on the Aurelle line until Friday\") that should not become a permanent rule. ALWAYS confirm the wording with the merchant before writing it — this changes what an autonomous system does with their money. It cannot change budgets, modes, autonomy or caps, and it cannot approve or dispatch anything.","tags":["auto_write"],"security":[{"bearerAuth":[]},{"oauth2":["write"]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"action":{"type":"string","enum":["add_instruction","remove_instruction","note"],"description":"add_instruction: a standing rule. remove_instruction: retract one, exact wording. note: a one-off fact for the next cycle only."},"text":{"type":"string","description":"One line, in the merchant's own words where possible. For remove_instruction, the wording of the existing instruction as get_brain_thinking returned it."},"because":{"type":"string","description":"Short context: what the merchant said that led to this."}},"required":["action","text"]}}}},"responses":{"200":{"description":"Tool result","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"result":{}},"required":["ok","result"]}}}},"400":{"description":"`invalid_request` — The request is malformed or an argument is unusable. The message names which.\n\n`invalid_body` — The body is not a JSON object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"`unauthorized` — No credential was presented, or the one presented is not valid. This deliberately does not say which of expired, revoked or unknown it was: a 401 that tells those apart is an oracle for guessing tokens, and the remedy is identical for all three.\n\n`expired` — The credential verified and its expiry has passed.\n\n`address_not_allowed` — The credential verified and carries an IP allowlist that this request did not come from.\n\n`credential_rejected` — Diagnose could not resolve the credential at all.\n\n`no_credential` — Diagnose was called with no Authorization header, so there is nothing to report on.\n\n`invalid_client` — The client authentication on this request did not verify.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"`forbidden` — The credential is valid and does not hold a scope this tool requires.\n\n`insufficient_scope` — As `forbidden`, and the body names `required_scopes`. A webhook subscription needs both `automation:write` to configure the store and the read scope for every type it filters — a webhook is a read.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"`not_found` — No tool of that name is exposed to this credential. Note that a tool hidden by a missing scope answers `forbidden`, not this — the two are told apart deliberately.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"`idempotency_conflict` — This idempotency key was already used with DIFFERENT arguments. Replaying it would be a second, different write under a key that promises one.\n\n`idempotency_in_flight` — A request with this key is still running. This is not an error — it is \"ask again\", and the response carries `Retry-After`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"`payload_too_large` — The body exceeds the endpoint’s size bound.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"`rate_limited` — This credential exceeded its per-minute limit. The body carries the limit and the count, and the response carries `Retry-After` and the `RateLimit-*` headers.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"`server_error` — Something on our side failed. The request may or may not have taken effect.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"503":{"description":"`idempotency_unavailable` — The idempotency claim could not be taken, so the write was NOT attempted. This fails closed on purpose: running an unguarded write because the guard was unreadable is how a customer gets refunded twice.\n\n`unavailable` — A read could not be served just now. Nothing was written or exported.\n\n`rls_scoped_execution_unavailable` — This deployment is not currently running the row-level-scoped execution mode the headless surface requires, so it refuses rather than executing with wider reach than the grant.\n\n`corpus_unavailable` — The corpus this endpoint reads could not be loaded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}}}