Brand Studio docs
Engineering manual

APIs and execution contracts

The authenticated quote seam, execution lifecycle, provider adapters and complete source route inventory.

Authenticated no-spend quote

Observed source: hosted-app/app/api/query-manifest/route.js. POST authenticates first, requires run_pulls scope, uses a process-local 30-request/60-minute owner bucket and bounds JSON to 131072 bytes. API-key authentication attempts a last_used_at write: no-spend does not mean no-write.

With truthy pull_plan, the route compiles an explicit selection using server-controlled price evidence. Responses include ok, issues, manifest and spend_enabled; plan can be absent on early validation failure. A ready quote is not approval to dispatch and spend_enabled is a runtime switch, not consent.

Anonymous requests return 401/signup_required, wrong scope 403/insufficient_scope, rate limiting 429/too_many_manifests. JSON read failures use the bounded reader's status. Without pull_plan the same route uses the legacy manifest branch; it must be documented and tested separately.

{
  "domain": "example.com",
  "seo_market": {
    "location_code": 2840,
    "language_code": "en",
    "confirmed": true
  },
  "pull_plan": {
    "preset": "low",
    "items": {
      "own_rankings": {
        "included": true,
        "groups": [
          {
            "id": "g1",
            "label": "Domain / locale query 1",
            "included": true,
            "active_zero": false,
            "values": {
              "rows": 500
            }
          }
        ],
        "options": {
          "clickstream": "off"
        }
      }
    }
  },
  "plan_bindings": {}
}

Synthetic request example: one current-catalog own_rankings group, 500 rows, US/English. All omitted items are OFF. This is not an Alaris quote, current external price verification or purchase approval.

Selected-plan request fields

Field or conditionObserved contractBoundary
Authorization: BearerSupabase session token or Brand Studio API key with run_pulls scope.Authenticate before scope, rate bucket and body reading. Credentials never belong in JSON.
domain / urlTruthy domain takes precedence over url; the compiler normalizes an HTTP(S) hostname.Rejects credentials, explicit ports, IP/localhost and private-style suffixes. Syntax validation is not live public-DNS proof.
pull_planExplicit selection object; truthy value selects the selected-plan branch.Missing/falsy value uses the legacy branch. Omitted catalog rows mean OFF, not preset defaults.
seo_marketconfirmed=true, positive-integer location_code, lowercase language_code such as en or en-us.Required by the compiler; US/English fixtures use 2840/en. This is separate from the business address.
plan_bindingsObject, defaults to {} when omitted; null/arrays refused.social_profiles must be an array if present. Active items require their specific confirmed bindings.
tier / pull_profile_idResolved by pullTierForRequest; determines separate customer-credit pricing.Selected quote uses pull_profile_id or PULL_DEPTH_DEFAULT; legacy also considers depth. Preset and billing tier are not interchangeable.
user_id / client price fieldsNot ownership, pricing or authorization authority for this route.Uses auth.user.id and server-provided price evidence. Quoting does not grant purchase permission.
plan_hash / approved_max_cost_usdNot checked as approval by the quote route.The later reservation route recompiles and requires the exact current hash and maximum.

Selection and group structure

Field or conditionObserved contractBoundary
pull_plan.itemsCatalog-ID keyed rows, at most 111 supplied keys; unknown IDs refused.Current gap: {} and [] both reach ready with no supported selections.
items[id].included / groupsincluded must be boolean; groups must be an array.Group count is bounded by the item's catalog max_groups, otherwise 20.
groups[].id / valuesTruthy unique ID; values is a non-array object of integer quantities.Quantities are 0..100000, then catalog and actual adapter ceilings also apply. Labels are not target bindings.
groups[].included / active_zeroincluded=false omits a group; active_zero requests a zero-result probe.Executable alpha refuses active_zero; zero quantity never silently buys a minimum request.
items[id].optionsCatalog-defined options; selected groups/options must survive normalization unchanged.Unsupported changes are refused, not silently clamped. Clickstream stays off.

Confirmed target bindings

Field or conditionObserved contractBoundary
rival_rankings[group_id] / keyword_gaps[group_id]Different public domain, confirmed=true, nonblank reason; optional source_url.No duplicate rival domain within a collection; search overlap alone is not business-competitor confirmation.
reviews[group_id]Confirmed same-domain website plus supported place_id/cid or public Maps URL parameters; optional name/source_url.No short-link expansion or guessed identity. Conflicting IDs and duplicate normalized profiles refused; CID strings preserve large integers.
website_urlsExactly the selected fc_pages count; unique absolute same-domain non-PDF URLs.No hidden discovery; requires separate server-verified Firecrawl credit-cost evidence.
social_profiles[]Exactly one confirmed {platform,url} for each selected platform.Allowed platform hostname and nonempty profile path; labels are not account bindings.
lighthouse[group_id]Owned target/subdomain URL plus device=desktop|mobile; one test per group.Unique page/device pairs; site-evidence combined ceiling is 30 tasks/groups.
ads.facebookconfirmed=true, numeric page_id of 5..30 digits, Facebook source_url.One bound list request; no implicit pagination or detail purchases.

Selected quote response fields

Field or conditionObserved contractBoundary
status / ok / issuesready/true or needs_review/false; issues[] contains item_id, reason and optional detail.Compiler review state, not authorization. Early auth/body refusals can omit ok/issues.
planCompiled source-bound execution plan; some later refusals can include it.Early validation failures omit plan entirely; do not assume every HTTP 422 has one.
plan.schema_version / rate_versionbrand-studio-execution/v1 and the versioned local rate basis.Version labels are not proof that current external provider rates were refreshed.
plan.execution_contractadapter_version, source_sha256, sorted capability_ids.Binds producer dependency sources; source changes invalidate the prior approval context.
plan.domain / market / preset / selectionCanonical target, confirmed locale, preset and normalized explicit selection.Do not reconstruct a purchase from a preset label or omitted rows.
plan.rows[]item_id, status, groups, max_cost_usd; leg/rate_id/source_ids when applicable.Supported, parked, unsupported, conditional-not-implemented and not-selected are different. None is an execution receipt.
plan.legs / plan_hashseo, backlinks, reviews, website, social, site_evidence and ads configurations; each carries domain, market, max_cost_usd, plan_id.Every leg plan_id equals plan_hash. Rival/gap and site-evidence groups retain individual targets and quantities.
plan.max_cost_usd / price_kindProvider maximum in USD; maximum_provider_cost_not_guaranteed_charge.Not actual charge, account movement or full report cost.
plan.customer_credit_pricecredits, basis, tier, byo_discount_resolved_at_reservation.Existing hosted-tier price, separate from provider dollars; final billing rules still require reservation/ledger proof.
manifestquery-manifest-v1 view; null on compile failure.Successful projection uses reserved_execution_plan / approved_scope labels even before reservation. These labels do not prove approval or stored execution.
spend_enabledExact switch BRAND_STUDIO_PLAN_RUNS_ENABLED==='true'.True does not prove human approval, server cap, sufficient credits, built runtime or operator approval ID.

Limits, errors and side effects

Field or conditionObserved contractBoundary
Authentication/scope401 refused/signup_required + detail; 403 refused/insufficient_scope + required_scope.Real route/auth modules exercised with synthetic transport, not a live account.
Rate limiting429 rate_limited/too_many_manifests after 30 allowed attempts in a 60-minute owner bucket.Process-local fixed window from first use, not wall-clock-hour or distributed quota. Restart resets it; above 5000 owners the Map clears.
Body size/JSON131072 bytes; 413 body_too_large; 400 invalid_json.Streamed and advertised length checked. Authentication and allowed-request quota consumption occur first.
Read timeoutsBody read 10 seconds; 504 request_deadline, 499 request_aborted, 502 readable_body_required.Source-defined, not measured in this probe. Route maxDuration=30 is deployment configuration, not a measured latency promise.
Compilation refusal422 needs_review with issues and manifest=null; plan may be omitted.Includes invalid_domain, market_confirmation_required, invalid_bindings, explicit_plan_required, invalid_selection, invalid_quantity, unsupported_selection, parked_module and item-specific reasons.
API-key auth side effectHashed-key lookup then best-effort PATCH of last_used_at.Happens before scope/quota/body checks. No-provider-spend is NOT no-database-write.
Session authenticationGET /auth/v1/user; 15-second / 1-MiB bound.Does not update API-key usage. Fixture observations made no live auth request.
Execution is a later gateQueue mode, exact current hash/maximum, built runtime, enabled server cap and exact operator plan/user/approval ID.No settings changed. The previous funded attempt is not reusable permission.

Observed gaps and verification status

On 2026-09-13 the coordinator exercised the actual Next route, auth resolver and compiler with synthetic auth transport only. Eight observations record response keys, issues, selected items and auth side effects; external fetches are refused. This is module-level runtime evidence, not a listening HTTP server, live identity or independent funded acceptance.

Empty items={} and items=[] both returned HTTP 200/ready with no selected capabilities, maximum provider cost 0 and fixture tier price 1 credit. This is a readiness/schema gap, not proof that a charge occurred. Require nonempty executable scope and an object-shaped items record before acceptance.

A synthetic Firecrawl price-basis timestamp of 2000-01-01 was accepted when unit cost/source were otherwise valid. The source checks parseability but no freshness window. Lead review must establish expiry policy and stale-source refusal; this pass does not invent a threshold or activate the provider.

Existing route/compiler/review tests pass but do not cover empty-scope refusal or age-expiry requirements. Implementation and new independent tests remain assigned to the Claude engineers, whose retry gate is unresolved.

Quote source and reproduction

Sources: hosted-app/app/api/query-manifest/route.js; hosted-app/lib/pull-execution-plan.mjs; hosted-app/lib/comprehensive-pull.mjs (resolveAuthContext/resolveApiKeyContext); hosted-app/lib/bounded-http.mjs; hosted-app/lib/comprehensive-pull-handler.mjs (reservation approval gates); hosted-app/lib/plan-review-response.mjs.

Run node docs/plans/brand-studio-roadmap/quote-contract-observation-20260913.mjs for the no-external-network fixture observation. Its paired .json records observations, probe hash and execution source hash. Synthetic prices and identities must never be copied into a live quote or approval.

Existing checks: node --test tests/query-manifest-route.test.mjs tests/pull-execution-plan.test.mjs tests/plan-review-response.test.mjs. The remaining route inventory still needs field-level coverage; this reference does not certify every API.

Core public seams

SeamResponsibilityRequired contract proof
POST /api/comprehensiveValidate current approval and reserve a selected plan for execution.Reject changed digest, wrong owner, unresolved targets and insufficient credits before purchase.
GET /api/comprehensive; POST /api/comprehensive/advanceObserve or advance the stored run through existing queue contracts.Document polling side effects; prove fencing, process restart and closed-tab continuation.
GET /api/creditsReturn authenticated complete balance and ledger state.Missing RPC, malformed snapshot or partial read cannot look like available credit.
GET /api/pulls; /pull/[pull_id]Find and reload the same saved result.Ownership, terminal status and retained evidence survive browser and process reload.
GET /api/brand-studio/brand-kit; /brand-presenceExport from the selected saved capture.Export, visible claims and receipt agree; private fields and signed credentials do not leak.
POST /api/brand-studio/retry-legLegacy retry seam with selected-plan protection.Existing focused tests refuse selected-plan retries before write or collection; broader runtime proof remains required.
POST /api/mcpProgrammatic workflow via scoped tools.Each tool maps to the same authority, versions, approval and cost gates as the HTTP path.

Every endpoint needs a contract

Complete the route inventory with request/response schemas, auth and ownership scope, pagination and ordering, error status/reason codes, input size limits, timeouts, rate limits, idempotency, side effects and version compatibility.

Include webhook/cron, download, dynamic path and non-/api handlers where relevant. The generated inventory below discovers local /api route declarations; it does not prove deployed availability or exhaustive middleware behavior.

For each provider adapter, document exact endpoint/version, request quantities and units, locale, target identity, task ownership, paging/continuation, retry/cancel policy, response schema, rate basis, actual-cost extraction and normalization. Link official provider documentation through the existing provider pages and refresh drift-prone details before use.

Supported, parked and legacy behavior

Catalog support, adapter implementation, configuration and observed successful execution are four different states. Show all four. A free item with no working adapter is not complete. A configured key does not authorize new work.

The request ledger refuses new spending when an earlier request is reserved or uncertain. A free poll must belong to the same plan, provider task and target. Make these properties explicit at every adapter and continuation seam.

Process-local daily provider ceilings are not shared account-wide limits and can reset on process restart. Distributed enforcement and concurrent-account reconciliation require separate evidence.

AI visibility and unpriced lanes stay parked. Existing legacy stages may still be present in queue ordering; selected-plan progress must expose them truthfully as skipped instead of implying active purchased work.

Route declarations from current source

This inventory is regenerated from route files. A declared method does not prove authentication, schema coverage, runtime success or support status. Contract coverage must be checked separately.

MethodRouteSource
DELETE, GET, POST/api/account/api-keyshosted-app/app/api/account/api-keys/route.js
DELETE, GET/api/account/mcp-connectionshosted-app/app/api/account/mcp-connections/route.js
GET, POST/api/adminhosted-app/app/api/admin/route.js
POST/api/admin/topuphosted-app/app/api/admin/topup/route.js
POST/api/auth/google/exchangehosted-app/app/api/auth/google/exchange/route.js
GET/api/auth/googlehosted-app/app/api/auth/google/route.js
POST/api/authhosted-app/app/api/auth/route.js
POST/api/brand-checkhosted-app/app/api/brand-check/route.js
POST/api/brand-refreshhosted-app/app/api/brand-refresh/route.js
POST/api/brand-studio/ai-linkhosted-app/app/api/brand-studio/ai-link/route.js
GET/api/brand-studio/brand-deltahosted-app/app/api/brand-studio/brand-delta/route.js
GET/api/brand-studio/brand-kithosted-app/app/api/brand-studio/brand-kit/route.js
GET/api/brand-studio/brand-presencehosted-app/app/api/brand-studio/brand-presence/route.js
GET/api/brand-studio/proxy-assethosted-app/app/api/brand-studio/proxy-asset/route.js
POST/api/brand-studio/retry-leghosted-app/app/api/brand-studio/retry-leg/route.js
POST/api/brand-studio/run-overridehosted-app/app/api/brand-studio/run-override/route.js
POST/api/brand-studio/visual-directionhosted-app/app/api/brand-studio/visual-direction/route.js
GET/api/brands/basic/changeshosted-app/app/api/brands/basic/changes/route.js
DELETE, GET, POST/api/brands/basichosted-app/app/api/brands/basic/route.js
GET/api/brands/change-evidencehosted-app/app/api/brands/change-evidence/route.js
GET/api/brands/changeshosted-app/app/api/brands/changes/route.js
GET/api/brands/exporthosted-app/app/api/brands/export/route.js
GET/api/brands/review-changeshosted-app/app/api/brands/review-changes/route.js
GET, POST/api/brands/review-watchhosted-app/app/api/brands/review-watch/route.js
GET, POST/api/brands/review-watch/sweephosted-app/app/api/brands/review-watch/sweep/route.js
POST/api/brands/reviews/snapshothosted-app/app/api/brands/reviews/snapshot/route.js
GET/api/brandshosted-app/app/api/brands/route.js
GET/api/brands/search-changeshosted-app/app/api/brands/search-changes/route.js
GET, POST/api/brands/search-watchhosted-app/app/api/brands/search-watch/route.js
GET, POST/api/brands/search-watch/sweephosted-app/app/api/brands/search-watch/sweep/route.js
GET, POST/api/brands/site-pageshosted-app/app/api/brands/site-pages/route.js
GET/api/brands/social-changeshosted-app/app/api/brands/social-changes/route.js
GET, POST/api/brands/social-watchhosted-app/app/api/brands/social-watch/route.js
GET, POST/api/brands/social-watch/sweephosted-app/app/api/brands/social-watch/sweep/route.js
GET, POST/api/brands/watchhosted-app/app/api/brands/watch/route.js
GET, POST/api/brands/watch/sweephosted-app/app/api/brands/watch/sweep/route.js
DELETE, GET, POST/api/byo-keyshosted-app/app/api/byo-keys/route.js
POST/api/client-review/ai-fixhosted-app/app/api/client-review/ai-fix/route.js
GET, POST/api/client-review/overrideshosted-app/app/api/client-review/overrides/route.js
POST/api/client-review/requests/[id]hosted-app/app/api/client-review/requests/[id]/route.js
GET/api/client-review/requestshosted-app/app/api/client-review/requests/route.js
POST/api/client-review/roundshosted-app/app/api/client-review/rounds/route.js
POST/api/client-review/shares/[id]/revokehosted-app/app/api/client-review/shares/[id]/revoke/route.js
GET, POST/api/client-review/shareshosted-app/app/api/client-review/shares/route.js
GET, POST/api/comprehensive/advancehosted-app/app/api/comprehensive/advance/route.js
GET/api/comprehensive/previewhosted-app/app/api/comprehensive/preview/route.js
GET, POST/api/comprehensivehosted-app/app/api/comprehensive/route.js
POST/api/comprehensive/sweephosted-app/app/api/comprehensive/sweep/route.js
GET, POST/api/credits/checkouthosted-app/app/api/credits/checkout/route.js
GET/api/creditshosted-app/app/api/credits/route.js
POST/api/credits/topuphosted-app/app/api/credits/topup/route.js
POST/api/credits/webhookhosted-app/app/api/credits/webhook/route.js
POST/api/docs-askhosted-app/app/api/docs-ask/route.js
GET/api/instanthosted-app/app/api/instant/route.js
POST/api/intake-peekhosted-app/app/api/intake-peek/route.js
POST/api/interview-prefillhosted-app/app/api/interview-prefill/route.js
GET/api/local-runs/[run_id]hosted-app/app/api/local-runs/[run_id]/route.js
GET/api/local-runshosted-app/app/api/local-runs/route.js
GET, POST/api/mcp/oauth/authorizehosted-app/app/api/mcp/oauth/authorize/route.js
POST/api/mcp/oauth/revokehosted-app/app/api/mcp/oauth/revoke/route.js
POST/api/mcp/oauth/tokenhosted-app/app/api/mcp/oauth/token/route.js
DELETE, GET, POST/api/mcphosted-app/app/api/mcp/route.js
POST/api/notifications/dispatchhosted-app/app/api/notifications/dispatch/route.js
GET, POST/api/notifications/outboxhosted-app/app/api/notifications/outbox/route.js
GET, POST/api/notifications/preferenceshosted-app/app/api/notifications/preferences/route.js
GET, POST/api/notifications/sweephosted-app/app/api/notifications/sweep/route.js
GET, POST/api/notifications/unsubscribehosted-app/app/api/notifications/unsubscribe/route.js
POST/api/pulls/complete-sectionhosted-app/app/api/pulls/complete-section/route.js
GET/api/pullshosted-app/app/api/pulls/route.js
POST/api/pulls/section-review-planhosted-app/app/api/pulls/section-review-plan/route.js
GET, POST/api/pulls/section-review-retryhosted-app/app/api/pulls/section-review-retry/route.js
POST/api/query-manifesthosted-app/app/api/query-manifest/route.js
GET/api/review/[token]/mediahosted-app/app/api/review/[token]/media/route.js
POST/api/review/[token]/requestshosted-app/app/api/review/[token]/requests/route.js
GET/api/review/[token]hosted-app/app/api/review/[token]/route.js
GET/api/searchhosted-app/app/api/search/route.ts

On this page