---
title: "Answer Marketing Agent setup step"
method: POST
path: "/api/apps/{app_id}/cmo/setup/answer"
---

# Answer Marketing Agent setup step

`POST /api/apps/{app_id}/cmo/setup/answer`

<Info>This API is in beta. Endpoints, fields, and behavior may still change, so avoid depending on it in production.</Info>

Answers the setup step the Marketing Agent is waiting on with a fixed choice, without an agent turn.

Send the state's `setup_stage` as `stage`, along with its `reset_epoch`. Each step takes its own answers:
- `channels` takes `channels_ok`, which approves the channels and moves setup to `kpis`.
- `kpis` takes `kpis_ok`, which approves the KPIs and moves setup to `budget`.
- `budget` takes `budget` and `competitor_research`, together or one at a time. Once both are known, setup moves to `generating` and the agent writes the strategy. Send `budget_go` on its own to confirm a pair that's already stored.

To ask for a change instead, use [Send Marketing Agent message](/api-reference/send-marketing-agent-message).

Most answers are free and take effect at once. The one that completes the budget step starts the strategy run. The work runs in the background after the call returns. Poll [Get Marketing Agent state](/api-reference/get-marketing-agent-state) until `status` is `ready` or `error`. If `competitor_scan_status` is `running`, keep polling until it is no longer `running`.

Each run is a Superagent turn, charged to the app's workspace in message credits based on the tokens it uses. A run can take several minutes and is stopped after 15. If the workspace is out of credits the run doesn't start, and `last_error` says so.

To retry safely, send the same `message_id` or `Idempotency-Key` header again. A retry returns the current state and applies nothing, so it never starts a second strategy run.

This is limited to 120 requests a minute per app for each workspace's personal API keys, so every key in a workspace shares one allowance, across every Marketing Agent endpoint. Some workspaces have a different limit.

<Note>This endpoint accepts a personal API key belonging to a user with editor access to the app and an editor role in its workspace. Read-only keys and workspace API keys are refused.</Note>

<Warning>The response includes fields beyond the ones documented here. Don't rely on undocumented response fields, as they can change at any time. Send only the fields documented here. Other request fields are not supported and their behavior can change.</Warning>

## Path parameters

- `app_id` string, required — ID of the app.

## Headers

- `Idempotency-Key` string, nullable — Your key for this answer, as an alternative to `message_id`. A retry with the same key returns the current state and applies nothing. Ignored when the body carries `message_id`.

## Request body

- SetupAnswerPayload
  - `stage` string, required — The step you're answering, from the state's `setup_stage`. Either `"channels"`, `"kpis"`, or `"budget"`.
  - `reset_epoch` integer, nullable — The state's `reset_epoch`, so the answer is rejected if the plan was reset since you read it. Omit it to skip that check.
  - `answers` SetupAnswerItem[], required — Answers to the step's questions. Between 1 and 4.
    - `key` string, required — Question being answered. Either `"channels_ok"`, `"kpis_ok"`, `"budget"`, `"competitor_research"`, or `"budget_go"`.
    - `selected_label` string, required — The answer as the user would say it. It's recorded as their message in the app's chat, and for `budget` it's the stored budget.
    - `value` boolean, nullable — For `competitor_research`, whether to research competitors (`true`) or not (`false`). Ignored for other questions.
    - `band` string, nullable — For `budget`, the budget band that selects each KPI's target. Either `"none"`, `"low"`, `"mid"`, or `"high"`. Any other value is ignored.
  - `message_id` string, uuid — ID for the chat message that records the answer. Use a new UUID for every answer, and the same one when you retry it. A retry with the same ID returns the current state and applies nothing. Defaults to a new UUID.

## Response `200`

Successful Response

- MarketingAgentStateResponse — The Marketing Agent's plan for an app and the state of its latest run.
  - `status` 'idle' | 'provisioning' | 'analyzing' | 'ready' | 'error', required — State of the latest run. Either `"idle"` before any run, `"provisioning"` while the workspace's Marketing Agent is created, `"analyzing"` while a run is working, `"ready"` once it finished, or `"error"` if it failed. A run that stops reporting for 15 minutes reads as `"error"`.
  - `run_id` string, nullable, required — ID of the latest run, or `null` if no run has started. Compare it between polls to tell a new run from the one you started.
  - `last_error` string, nullable, required — Why the latest run failed, or `null` if it didn't.
  - `setup_stage` 'not_started' | 'scanning' | 'channels' | 'kpis' | 'budget' | 'generating' | 'complete', required — How far setup has got. Either `"not_started"`, `"scanning"`, `"channels"`, `"kpis"`, `"budget"`, `"generating"`, or `"complete"`. At `channels`, `kpis`, and `budget` the agent is waiting for an answer.
  - `reset_epoch` integer, required — Number of times the plan was reset. Send it back as `reset_epoch` when you answer a setup step.
  - `channels` MarketingChannel[], required — The marketing channels the agent proposed. Empty until the opening scan finishes.
    - `name` string, required — Name of the channel.
    - `why` string, required — Why this channel fits the app.
    - `tactics` string[], required — Concrete tactics for the channel. Up to 3.
    - `priority` integer, nullable — How much to invest in this channel, from `1` to `5`, where `5` means invest here first. Omitted when the agent didn't rank it.
  - `channels_revision_suggestion` string, nullable, required — A change to the channels the agent offers to make, or `null` if it offers none. Send it as a message to accept it.
  - `kpis` MarketingKpi[], required — The KPIs the agent proposed. Empty until the opening scan finishes.
    - `name` string, required — The KPI in one measurable line.
    - `why` string, required — Why this KPI matters for the app.
    - `baseline` string, nullable — Current value, when the agent could tell it from the app. Omitted otherwise.
    - `target` string, nullable — Target at the recommended budget, with a time horizon.
    - `targets` object, nullable — The target at each budget band, keyed by `none`, `low`, `mid`, and `high`. A band that's missing means spend at that level can't move this KPI.
    - `target_values` object, nullable — The number inside each band's target, keyed like `targets`.
    - `metric` KpiMetric
      - `template` 'event_count' | 'unique_sessions' | 'unique_users' | 'retention_dn', required — How the KPI is counted. Either `"event_count"`, `"unique_sessions"`, `"unique_users"`, or `"retention_dn"`.
      - `event_name` string, required — Analytics event the KPI is measured against.
      - `window_days` integer, nullable — Number of days the value is counted over. Either `7` or `30`.
      - `n` integer, nullable — Day the return is measured at, for a `retention_dn` KPI only.
    - `instrument` KpiInstrument
      - `builder_prompt` string, required — Prompt for the app builder that adds the missing analytics event. Present only while the app doesn't record `metric.event_name` yet.
      - `where` string, nullable — Where in the app's code the outcome already happens. Omitted when the agent didn't find it.
      - `requested_at` string, nullable — When the build that adds the event was requested, as an ISO 8601 timestamp, or `null` if it hasn't been.
  - `kpis_revision_suggestion` string, nullable, required — A change to the KPIs the agent offers to make, or `null` if it offers none. Send it as a message to accept it.
  - `strategy` MarketingStrategy, required
    - `summary` string, required — The strategy in up to 2 sentences.
    - `positioning` string, nullable — How the app is positioned against its competitors.
    - `channel_plan` MarketingChannel[], required — The channels the strategy invests in.
      - `name` string, required — Name of the channel.
      - `why` string, required — Why this channel fits the app.
      - `tactics` string[], required — Concrete tactics for the channel. Up to 3.
      - `priority` integer, nullable — How much to invest in this channel, from `1` to `5`, where `5` means invest here first. Omitted when the agent didn't rank it.
    - `key_metrics` string[], required — The metrics the strategy is judged by.
    - `budget_fit` string, nullable — How the strategy fits the workspace's stated budget.
  - `suggestions` MarketingSuggestion[], required — Growth ideas the agent generated for the app.
    - `id` string, required — ID of the suggestion.
    - `title` string, required — Short title of the growth idea.
    - `builder_prompt` string, required — Prompt to send to the app builder to build the idea.
    - `category` string, nullable — Kind of growth idea.
    - `rationale` string, nullable — Why the idea should help.
    - `impact` string, nullable — Expected impact. Either `"high"`, `"medium"`, or `"low"`.
    - `effort` string, nullable — Rough build effort. Either `"low"`, `"medium"`, or `"high"`.
    - `dismissed` boolean, nullable — Whether someone dismissed the suggestion.
  - `budget` MarketingBudget, required
    - `description` string, required — The budget as it was stated.
    - `band` 'none' | 'low' | 'mid' | 'high', nullable — Budget band that selects each KPI's target. Either `"none"`, `"low"`, `"mid"`, or `"high"`. Omitted when the budget was stated in free text.
  - `competitor_research_opt_in` boolean, required — Whether competitor research runs alongside the strategy.
  - `competitor_research_answer` boolean, nullable, required — Whether competitor research was accepted (`true`) or declined (`false`), or `null` if the question wasn't answered yet.
  - `setup_paused` boolean, required — Whether setup is paused because the user asked the agent to stop. Sending a message resumes it.
  - `competitors` MarketingCompetitor[], required — Competitors found by the research, with verified URLs.
    - `name` string, required — Name of the competitor.
    - `url` string, required — URL of the competitor's site.
    - `angle` string, required — How the competitor positions itself.
    - `gap` string, required — What the competitor leaves open for this app.
  - `competitor_scan_status` 'running' | 'done' | 'failed', nullable, required — State of the competitor research. Either `"running"`, `"done"`, or `"failed"`, or `null` if it never ran.
  - `cmo_agent_app_id` string, nullable, required — ID of the workspace's Marketing Agent app your conversation runs on, or `null` if you haven't talked to it on this app yet.

## Other responses

- `400` — The answers don't match the questions `stage` asks.
- `401` — Missing or invalid credentials.
- `403` — You don't have editor access to this app or an editor role in its workspace, the app is a mobile app, Superagent is turned off for the workspace, or you used a read-only or workspace API key.
- `404` — App not found, or `stage` isn't `channels`, `kpis`, or `budget`.
- `409` — Setup isn't on `stage` any more, the plan was reset since `reset_epoch`, the agent is still working, `budget_go` was sent before both budget answers were stored, or `message_id` was already used for a different message.
- `422` — The body is malformed, the `budget` answer has an empty `selected_label`, the `competitor_research` answer has no `value`, or `Idempotency-Key` is longer than 100 characters.
- `429` — Rate limit exceeded.

## Changes

- **2026-09-30** `e2a6a9f1fe4c` — 1 info
  - endpoint added

[Change history](https://skmtc.dev/idealspot/apis/base44-app-management-api/changes/api/apps/:app_id/cmo/setup/answer/post.md)

---

[API](https://skmtc.dev/idealspot/apis/base44-app-management-api.md) · [All operations](https://skmtc.dev/idealspot/apis/base44-app-management-api/llms.txt) · [OpenAPI document](https://skmtc.dev/idealspot/apis/base44-app-management-api/revisions/e58d4ff7b1f8?raw)
