curl --request POST \
--url https://app.base44.com/api/apps/{app_id}/cmo/setup/answer \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"stage": "budget",
"answers": [
{
"key": "budget",
"selected_label": "$500 a month",
"value": true,
"band": "mid"
}
],
"reset_epoch": 0,
"message_id": "3f2504e0-4f89-11d3-9a0c-0305e82c3301"
}
'import requests
url = "https://app.base44.com/api/apps/{app_id}/cmo/setup/answer"
payload = {
"stage": "budget",
"answers": [
{
"key": "budget",
"selected_label": "$500 a month",
"value": True,
"band": "mid"
}
],
"reset_epoch": 0,
"message_id": "3f2504e0-4f89-11d3-9a0c-0305e82c3301"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
stage: 'budget',
answers: [{key: 'budget', selected_label: '$500 a month', value: true, band: 'mid'}],
reset_epoch: 0,
message_id: '3f2504e0-4f89-11d3-9a0c-0305e82c3301'
})
};
fetch('https://app.base44.com/api/apps/{app_id}/cmo/setup/answer', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://app.base44.com/api/apps/{app_id}/cmo/setup/answer",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'stage' => 'budget',
'answers' => [
[
'key' => 'budget',
'selected_label' => '$500 a month',
'value' => true,
'band' => 'mid'
]
],
'reset_epoch' => 0,
'message_id' => '3f2504e0-4f89-11d3-9a0c-0305e82c3301'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://app.base44.com/api/apps/{app_id}/cmo/setup/answer"
payload := strings.NewReader("{\n \"stage\": \"budget\",\n \"answers\": [\n {\n \"key\": \"budget\",\n \"selected_label\": \"$500 a month\",\n \"value\": true,\n \"band\": \"mid\"\n }\n ],\n \"reset_epoch\": 0,\n \"message_id\": \"3f2504e0-4f89-11d3-9a0c-0305e82c3301\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://app.base44.com/api/apps/{app_id}/cmo/setup/answer")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"stage\": \"budget\",\n \"answers\": [\n {\n \"key\": \"budget\",\n \"selected_label\": \"$500 a month\",\n \"value\": true,\n \"band\": \"mid\"\n }\n ],\n \"reset_epoch\": 0,\n \"message_id\": \"3f2504e0-4f89-11d3-9a0c-0305e82c3301\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.base44.com/api/apps/{app_id}/cmo/setup/answer")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"stage\": \"budget\",\n \"answers\": [\n {\n \"key\": \"budget\",\n \"selected_label\": \"$500 a month\",\n \"value\": true,\n \"band\": \"mid\"\n }\n ],\n \"reset_epoch\": 0,\n \"message_id\": \"3f2504e0-4f89-11d3-9a0c-0305e82c3301\"\n}"
response = http.request(request)
puts response.read_body{
"status": "ready",
"run_id": "68a1c9b7f0b3d9001a7e5d04",
"last_error": "The scan did not complete. Retry the setup.",
"setup_stage": "channels",
"reset_epoch": 0,
"channels": [
{
"name": "Reddit communities",
"why": "Freelancers already trade CRM tips in r/freelance.",
"tactics": [
"Share a build-in-public thread",
"Answer lead-tracking questions"
],
"priority": 5
}
],
"channels_revision_suggestion": "Swap LinkedIn for Product Hunt",
"kpis": [
{
"name": "Weekly signups",
"why": "Signups are the first sign the Reddit threads convert.",
"baseline": "About 10 a week",
"target": "50 a week in 90 days",
"targets": {
"mid": "50 a week in 90 days",
"none": "20 a week in 90 days"
},
"target_values": {
"mid": 50,
"none": 20
},
"metric": {
"template": "event_count",
"event_name": "lead_created",
"window_days": 7,
"n": 7
},
"instrument": {
"builder_prompt": "Track a lead_created event when a lead is saved on the Leads page.",
"where": "src/pages/Leads.jsx:42",
"requested_at": "2026-08-24T09:20:00+00:00"
}
}
],
"kpis_revision_suggestion": "Track paid conversions instead of signups",
"strategy": {
"summary": "Win early freelancers in the communities they already use, then turn them into referrals.",
"channel_plan": [
{
"name": "Reddit communities",
"why": "Freelancers already trade CRM tips in r/freelance.",
"tactics": [
"Share a build-in-public thread",
"Answer lead-tracking questions"
],
"priority": 5
}
],
"key_metrics": [
"Weekly signups",
"Week-one retention"
],
"positioning": "The CRM for freelancers who never wanted a CRM.",
"budget_fit": "Organic first, with $100 a month for Reddit promoted posts."
},
"suggestions": [
{
"id": "68a1d0c2f0b3d9001a7e5e10",
"title": "Add a referral link to the dashboard",
"builder_prompt": "Add a Refer a friend card to the dashboard with a copyable invite link.",
"category": "Referral",
"rationale": "Freelancers recommend tools to each other.",
"impact": "high",
"effort": "low",
"dismissed": false
}
],
"budget": {
"description": "$500 a month",
"band": "mid"
},
"competitor_research_opt_in": true,
"competitor_research_answer": true,
"setup_paused": false,
"competitors": [
{
"name": "HubSpot",
"url": "https://www.hubspot.com",
"angle": "A full CRM suite for sales teams.",
"gap": "Too heavy for a solo freelancer."
}
],
"competitor_scan_status": "done",
"cmo_agent_app_id": "68a1c2e4f0b3d9001a7e5c21"
}Answer Marketing Agent setup step
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:
channelstakeschannels_ok, which approves the channels and moves setup tokpis.kpistakeskpis_ok, which approves the KPIs and moves setup tobudget.budgettakesbudgetandcompetitor_research, together or one at a time. Once both are known, setup moves togeneratingand the agent writes the strategy. Sendbudget_goon its own to confirm a pair that’s already stored.
To ask for a change instead, use 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 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.
curl --request POST \
--url https://app.base44.com/api/apps/{app_id}/cmo/setup/answer \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"stage": "budget",
"answers": [
{
"key": "budget",
"selected_label": "$500 a month",
"value": true,
"band": "mid"
}
],
"reset_epoch": 0,
"message_id": "3f2504e0-4f89-11d3-9a0c-0305e82c3301"
}
'import requests
url = "https://app.base44.com/api/apps/{app_id}/cmo/setup/answer"
payload = {
"stage": "budget",
"answers": [
{
"key": "budget",
"selected_label": "$500 a month",
"value": True,
"band": "mid"
}
],
"reset_epoch": 0,
"message_id": "3f2504e0-4f89-11d3-9a0c-0305e82c3301"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
stage: 'budget',
answers: [{key: 'budget', selected_label: '$500 a month', value: true, band: 'mid'}],
reset_epoch: 0,
message_id: '3f2504e0-4f89-11d3-9a0c-0305e82c3301'
})
};
fetch('https://app.base44.com/api/apps/{app_id}/cmo/setup/answer', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://app.base44.com/api/apps/{app_id}/cmo/setup/answer",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'stage' => 'budget',
'answers' => [
[
'key' => 'budget',
'selected_label' => '$500 a month',
'value' => true,
'band' => 'mid'
]
],
'reset_epoch' => 0,
'message_id' => '3f2504e0-4f89-11d3-9a0c-0305e82c3301'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://app.base44.com/api/apps/{app_id}/cmo/setup/answer"
payload := strings.NewReader("{\n \"stage\": \"budget\",\n \"answers\": [\n {\n \"key\": \"budget\",\n \"selected_label\": \"$500 a month\",\n \"value\": true,\n \"band\": \"mid\"\n }\n ],\n \"reset_epoch\": 0,\n \"message_id\": \"3f2504e0-4f89-11d3-9a0c-0305e82c3301\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://app.base44.com/api/apps/{app_id}/cmo/setup/answer")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"stage\": \"budget\",\n \"answers\": [\n {\n \"key\": \"budget\",\n \"selected_label\": \"$500 a month\",\n \"value\": true,\n \"band\": \"mid\"\n }\n ],\n \"reset_epoch\": 0,\n \"message_id\": \"3f2504e0-4f89-11d3-9a0c-0305e82c3301\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.base44.com/api/apps/{app_id}/cmo/setup/answer")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"stage\": \"budget\",\n \"answers\": [\n {\n \"key\": \"budget\",\n \"selected_label\": \"$500 a month\",\n \"value\": true,\n \"band\": \"mid\"\n }\n ],\n \"reset_epoch\": 0,\n \"message_id\": \"3f2504e0-4f89-11d3-9a0c-0305e82c3301\"\n}"
response = http.request(request)
puts response.read_body{
"status": "ready",
"run_id": "68a1c9b7f0b3d9001a7e5d04",
"last_error": "The scan did not complete. Retry the setup.",
"setup_stage": "channels",
"reset_epoch": 0,
"channels": [
{
"name": "Reddit communities",
"why": "Freelancers already trade CRM tips in r/freelance.",
"tactics": [
"Share a build-in-public thread",
"Answer lead-tracking questions"
],
"priority": 5
}
],
"channels_revision_suggestion": "Swap LinkedIn for Product Hunt",
"kpis": [
{
"name": "Weekly signups",
"why": "Signups are the first sign the Reddit threads convert.",
"baseline": "About 10 a week",
"target": "50 a week in 90 days",
"targets": {
"mid": "50 a week in 90 days",
"none": "20 a week in 90 days"
},
"target_values": {
"mid": 50,
"none": 20
},
"metric": {
"template": "event_count",
"event_name": "lead_created",
"window_days": 7,
"n": 7
},
"instrument": {
"builder_prompt": "Track a lead_created event when a lead is saved on the Leads page.",
"where": "src/pages/Leads.jsx:42",
"requested_at": "2026-08-24T09:20:00+00:00"
}
}
],
"kpis_revision_suggestion": "Track paid conversions instead of signups",
"strategy": {
"summary": "Win early freelancers in the communities they already use, then turn them into referrals.",
"channel_plan": [
{
"name": "Reddit communities",
"why": "Freelancers already trade CRM tips in r/freelance.",
"tactics": [
"Share a build-in-public thread",
"Answer lead-tracking questions"
],
"priority": 5
}
],
"key_metrics": [
"Weekly signups",
"Week-one retention"
],
"positioning": "The CRM for freelancers who never wanted a CRM.",
"budget_fit": "Organic first, with $100 a month for Reddit promoted posts."
},
"suggestions": [
{
"id": "68a1d0c2f0b3d9001a7e5e10",
"title": "Add a referral link to the dashboard",
"builder_prompt": "Add a Refer a friend card to the dashboard with a copyable invite link.",
"category": "Referral",
"rationale": "Freelancers recommend tools to each other.",
"impact": "high",
"effort": "low",
"dismissed": false
}
],
"budget": {
"description": "$500 a month",
"band": "mid"
},
"competitor_research_opt_in": true,
"competitor_research_answer": true,
"setup_paused": false,
"competitors": [
{
"name": "HubSpot",
"url": "https://www.hubspot.com",
"angle": "A full CRM suite for sales teams.",
"gap": "Too heavy for a solo freelancer."
}
],
"competitor_scan_status": "done",
"cmo_agent_app_id": "68a1c2e4f0b3d9001a7e5c21"
}Authorizations
Personal access token, sent as Authorization: Bearer <token>.
Headers
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.
1 - 100Path Parameters
ID of the app.
Body
The step you're answering, from the state's setup_stage. Either "channels", "kpis", or "budget".
40"budget"
Answers to the step's questions. Between 1 and 4.
1 - 4 elementsShow child attributes
Show child attributes
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.
x >= 00
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.
"3f2504e0-4f89-11d3-9a0c-0305e82c3301"
Response
Successful Response
The Marketing Agent's plan for an app and the state of its latest run.
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".
idle, provisioning, analyzing, ready, error "ready"
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.
"68a1c9b7f0b3d9001a7e5d04"
Why the latest run failed, or null if it didn't.
"The scan did not complete. Retry the setup."
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.
not_started, scanning, channels, kpis, budget, generating, complete "channels"
Number of times the plan was reset. Send it back as reset_epoch when you answer a setup step.
0
The marketing channels the agent proposed. Empty until the opening scan finishes.
Show child attributes
Show child attributes
A change to the channels the agent offers to make, or null if it offers none. Send it as a message to accept it.
"Swap LinkedIn for Product Hunt"
The KPIs the agent proposed. Empty until the opening scan finishes.
Show child attributes
Show child attributes
A change to the KPIs the agent offers to make, or null if it offers none. Send it as a message to accept it.
"Track paid conversions instead of signups"
The marketing strategy, or null until setup writes one.
Show child attributes
Show child attributes
Growth ideas the agent generated for the app.
Show child attributes
Show child attributes
The marketing budget given during setup, or null if none was given yet.
Show child attributes
Show child attributes
Whether competitor research runs alongside the strategy.
true
Whether competitor research was accepted (true) or declined (false), or null if the question wasn't answered yet.
true
Whether setup is paused because the user asked the agent to stop. Sending a message resumes it.
false
Competitors found by the research, with verified URLs.
Show child attributes
Show child attributes
State of the competitor research. Either "running", "done", or "failed", or null if it never ran.
running, done, failed "done"
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.
"68a1c2e4f0b3d9001a7e5c21"
Was this page helpful?