Brain API Reference
The Brain API is a public developer endpoint that exposes your MarketingSecrets.ai memory to any MCP-compatible client (Claude Desktop, Cursor, ChatGPT, or your own scripts). Authenticate with a per-user API key for read/write access to your entrepreneur profile, memory facts, and semantic memory.
Base URL & protocol
POST https://www.marketingsecrets.ai/api/mcp
JSON-RPC 2.0 over HTTP POST (Model Context Protocol). All requests must use HTTPS.
Authentication
Send your API key in the x-api-key header:
x-api-key: ms_YOUR_KEY
- Keys use the
ms_prefix + 64 hex characters (67 total). - Generate keys in Chief of Staff → Manage → Memories → Connect an AI you already use (Advanced tab). The plaintext is shown once and is never kept server-side — it's stored as a SHA-256 hash. Revoke anytime.
- Every method needs a key, including the handshake.
initialize,notifications/initialized, andpingall return-32001 Authentication requiredwithout one, so send your key on the very first request.
Tools
| Tool | Purpose |
|---|---|
memory_search | Keyword search across your Brain — every word of your query must appear somewhere in a fact, in any order. Args: query (required), limit (default 8). |
memory_get_profile | Returns your structured entrepreneur profile: business, niche, audience, focus. No args. |
memory_save | Saves a fact, indexed for both semantic search and structured retrieval. Args: content (required), category (default misc). |
memory_get_recent | Lists the most recent structured facts in chronological order. Args: limit (default 20). |
Categories for memory_save: business, audience, preferences, projects, imported, misc.
Inventory tools
These are read-only and paginated. They exist so a client can enumerate your Attractive Character material rather than having to already know a uuid.
| Tool | Purpose |
|---|---|
list_attractive_characters | Your Attractive Characters — id, name, tagline, character type, polarity, active flag. Args: query, limit, offset. |
get_attractive_character | One full character sheet: backstory, origin story, core flaw, pillars, epiphany moments, voice style, plus story and voice counts. Args: id (required). |
list_voices | The voice style sheets attached to your characters, with each sheet's length and first few hundred characters. Args: ac_profile_id, query, limit, offset. |
get_voice | One voice style sheet in full. Args: id (required). |
list_stories | A page of your story inventory, newest first — id, title, summary, themes, emotion. Args: ac_profile_id, limit, offset. |
search_stories | The same page, filtered. query matches title, summary, body, false belief, emotion and lesson as a substring, and themes and custom tags as a whole tag. Args: query (required), ac_profile_id, limit, offset. |
get_story | One story in full: body, hooks, tags, false belief and every narrative-arc field. Args: id (required). |
list_hooks | Your hook library — every candidate hook on your stories, one per row, each with the story_id it came from. Args: ac_profile_id, limit, offset. |
search_hooks | The same, filtered to hooks containing query. Args: query (required), ac_profile_id, limit, offset. |
list_customer_avatars | Your customer avatars, each as a full sheet — age range, platform, occupation, top fear, top desire, demographics, psychographics. Args: query, limit, offset. |
get_customer_avatar | One customer avatar sheet. Args: id (required). |
Paging. Every list tool takes limit (default 25, maximum 50 — a larger value is clamped, not rejected) and offset (default 0). The response is a JSON object with an items array and a page object:
{
"ok": true,
"kind": "stories",
"items": [{ "id": "…", "title": "…", "summary": "…", "themes": [], "emotion": "hope" }],
"page": {
"limit": 25,
"offset": 0,
"returned": 25,
"total": 873,
"has_more": true,
"next_offset": 25
},
"hint": "Showing 25 of 873 stories (offset 0). More exist — call again with offset: 25."
}
Page by copying page.next_offset into the next call's offset, and stop when page.has_more is false. page.total is the real number of rows matching your call — never treat one page as the whole library.
Three limits are worth knowing:
- Story embeddings are never returned.
list_storiesandsearch_storiesreturnid,ac_profile_id,title,summary,themesandemotiononly; callget_storyfor the body. - A page can be shortened to fit the response. If the rows on a page would exceed the response size budget, the tail is dropped,
truncated_for_sizeis set totrue, andpage.returned/page.next_offsetare adjusted so followingnext_offsetstill skips nothing. - Hook paging walks stories. Hooks live inside their story rows, so
list_hooks/search_hooksread a bounded window of stories per call and report it asscan. Page within the window withoffset; whenscan.cappedistruethe walk stopped short of your whole inventory, so continue withstory_offset: <scan.next_story_offset>andoffset: 0. Never conclude a hook does not exist from a single capped page.
Every inventory tool only ever sees the workspace that minted your key. An ac_profile_id argument is resolved against that workspace's own characters, so it accepts a character's name as well as its uuid.
Every tool returns its payload as text on the JSON-RPC envelope at result.content[0].text. The sections below give the exact shape of that text per tool.
memory_search
Keyword search over your business brain. It covers the facts you saved, facts a teammate shared into the workspace, and the Team Brain.
Your query is split into words, and a fact matches when every word appears somewhere in its key or value, in any order — so stock watchlist finds a fact reading "current stock watchlist (Q3)". Matches come back newest-first.
When nothing matches every word, the search widens on its own, in order:
- Any word instead of all of them, ranked by how many words each fact hits.
- Translated and synonym keywords. A question asked in one language still finds a fact saved in another — asking
Aký je môj cieľreaches a fact stored as "September 2026 revenue target".
Short, distinctive queries are still the best input — one to four words like a name, a topic, or a metric. Fall back to memory_get_recent when a search comes up empty.
Arguments: query (string, required), limit (number, default 8)
Response lines are numbered and carry the category, plus the fact key when one exists:
1. [audience] ideal_customer: bootstrapped agencies doing $500K-$2M/yr
2. [business] pricing: $997 coaching, 4 slots per cohort
memory_get_profile
Returns the structured entrepreneur profile: business name, niche, audience, social handles, current focus.
Arguments: none ({})
Response text is one Label: value pair per line:
Business: Acme Agency
Niche: B2B SaaS marketing
Audience: bootstrapped SaaS founders doing $200K-$2M ARR
Current focus: launching a cold-email SOP
If nothing has been captured yet you get No entrepreneur profile found yet., and if the profile exists but is empty, Profile exists but is empty.
memory_save
Save a new fact to the brain. Indexed for semantic search and stored as a structured fact.
Arguments: content (string, required), category (string, default misc)
Returns a confirmation naming the category it filed under, e.g. Saved to brain (audience).
memory_get_recent
List the most recent structured facts in chronological order — no semantic ranking.
Arguments: limit (number, default 20)
Response lines are [category] key: value, unnumbered:
[audience] ideal_customer: bootstrapped agencies doing $500K-$2M/yr
[projects] q3_launch: cold-email SOP ships Sept 12
Example: search your Brain
curl -X POST https://www.marketingsecrets.ai/api/mcp \
-H "x-api-key: ms_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "memory_search",
"arguments": { "query": "who is my ideal customer?", "limit": 5 }
}
}'
The same call in TypeScript:
const res = await fetch('https://www.marketingsecrets.ai/api/mcp', {
method: 'POST',
headers: {
'x-api-key': process.env.MS_BRAIN_KEY!,
'Content-Type': 'application/json',
},
body: JSON.stringify({
jsonrpc: '2.0',
id: 1,
method: 'tools/call',
params: {
name: 'memory_search',
arguments: { query: 'who is my ideal customer?', limit: 5 },
},
}),
});
const { result } = await res.json();
console.log(result.content[0].text);
Responses follow the JSON-RPC envelope — the text payload lives at result.content[0].text:
{
"jsonrpc": "2.0",
"id": 1,
"result": { "content": [{ "type": "text", "text": "…" }] }
}
Example: save a fact
curl -X POST https://www.marketingsecrets.ai/api/mcp \
-H "x-api-key: ms_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "memory_save",
"arguments": {
"content": "Ideal customer: bootstrapped agencies doing $500K-$2M/yr.",
"category": "audience"
}
}
}'
Error codes
Errors come back as JSON-RPC error objects. Check error.message for a human-readable explanation.
| Code | Name | Meaning |
|---|---|---|
-32700 | PARSE_ERROR | Body is not valid JSON |
-32600 | INVALID_REQUEST | Missing jsonrpc or method |
-32601 | METHOD_NOT_FOUND | Unknown method or tool |
-32602 | INVALID_PARAMS | Missing required argument |
-32603 | INTERNAL_ERROR | Server-side failure |
-32001 | UNAUTHORIZED | Missing or invalid x-api-key |
-32002 | PAYMENT_REQUIRED | The workspace's subscription is no longer active |
All but -32001 and -32002 are standard JSON-RPC 2.0 codes; those two are our own, in the implementation-defined server range.
-32002 is returned on tools/call only (HTTP 402) — initialize and tools/list keep working, so a client can still connect and see why. The error object's data carries action_url and action_label pointing at the page where the plan can be reactivated.
For developers building sub-apps
Sub-apps inside the MarketingSecrets ecosystem can read from and write to the shared Brain instead of building their own memory layer. A Brand Voice sub-app that learns the user's Attractive Character should write it to the Brain so every other tool — CoS chat, content calendar, cold-email agent — has it automatically.
See the Developer Guide for the full pattern.
Next
- Connect an AI You Already Use — setup for Claude Desktop, Cursor, ChatGPT
- MCP usage examples — real prompts and tool calls
- Developer Guide — build sub-apps on top of the Brain