The Account Intel API
Check whether a company is a fit for cents, research it, read its evidence-gated buying signals, buying center and outreach drafts, and run priced market analyses. Over REST for Clay and scripts, or over MCP for your AI agent.
Get started
Introduction
Everything the app produces, over REST and MCP: the research brief, the evidence-gated buying signals, the buying center, the outreach drafts and the market analyses. Use it from Clay, a script, an AI agent or any HTTP client. Every response is plain JSON.
https://app.aiseoworkflows.com/api/v1All endpoints are scoped to the workspace that owns the API key: your accounts, your included-run budget, your data keys. GET /api/v1 lists the endpoints; GET /api/v1/workspace says what is set up (model, people database, which analyses) and the key's limits. Every core workflow in the app is reachable here: research, the brief and each of its slices, people and emails, outreach drafts, analyses and their inputs, company lists, ICP screening, plays, plans (the fit check, outreach and call prep), the positioning and the cost ledger. Every run you start is a job you can follow and stop.
confirm: true.{
"name": "Account Intel API",
"version": 1,
"docs": "https://docs.aiseoworkflows.com/",
"mcp": "https://app.aiseoworkflows.com/mcp",
"endpoints": [
"GET /api/v1/workspace",
"GET /api/v1/accounts",
"GET /api/v1/accounts/{slug}",
"POST /api/v1/research",
"POST /api/v1/analyses/run",
"GET /api/v1/lists",
"POST /api/v1/qualify",
"POST /api/v1/plays/{id}/run",
"POST /api/v1/plans/run",
"GET /api/v1/jobs/{id}",
"POST /api/v1/jobs/{id}/stop",
"..."
]
}Get started
Authentication
Create a key in the app under Settings → API & MCP. The full key is shown once; only its hash is stored. The same page walks you through connecting Claude Code, Claude Desktop, Cursor and VS Code.
Send it as a Bearer token on every request, REST or MCP:
Authorization: Bearer ai_live_YOUR_KEYName keys per consumer (one for Clay, one for scripts, one for your agent) and revoke them individually. Each key is limited to 60 requests per minute; requests over the limit get
429 with a Retry-After header.
curl https://app.aiseoworkflows.com/api/v1/accounts \
-H "Authorization: Bearer $AI_KEY"const res = await fetch("https://app.aiseoworkflows.com/api/v1/accounts", {
headers: { Authorization: `Bearer ${process.env.AI_KEY}` },
});
const { accounts } = await res.json();import os, requests
res = requests.get(
"https://app.aiseoworkflows.com/api/v1/accounts",
headers={"Authorization": f"Bearer {os.environ['AI_KEY']}"},
)
accounts = res.json()["accounts"]Get started
Quickstart
Research is asynchronous: start a run, then poll the account until its status is ready. A run takes a few minutes.
- Start research.
POST /researchanswers immediately with the accountslug. - Poll the account.
GET /accounts/{slug}returns"status": "running"until the brief exists, then the full brief. - Go deeper when it matters. Run an analysis with
POST /analyses/run; anything above $0.05 asks forconfirm: truefirst.
POST /plans/run with "plan": "fit"): about $0.04 per 100 companies, and it tells you which ones are worth researching. See Plans.# 1. Start research (answers right away, runs a few minutes)
curl -X POST https://app.aiseoworkflows.com/api/v1/research \
-H "Authorization: Bearer $AI_KEY" \
-H "Content-Type: application/json" \
-d '{"company": "acme.com"}'
# 2. Poll until status is "ready"
curl https://app.aiseoworkflows.com/api/v1/accounts/acme-com \
-H "Authorization: Bearer $AI_KEY"const headers = {
Authorization: `Bearer ${process.env.AI_KEY}`,
"Content-Type": "application/json",
};
// 1. Start research
const start = await fetch("https://app.aiseoworkflows.com/api/v1/research", {
method: "POST", headers,
body: JSON.stringify({ company: "acme.com" }),
}).then((r) => r.json());
// 2. Poll until the brief is ready
let brief;
do {
await new Promise((r) => setTimeout(r, 20000));
brief = await fetch(`https://app.aiseoworkflows.com/api/v1/accounts/${start.slug}`, { headers }).then((r) => r.json());
} while (brief.status === "running");import os, time, requests
H = {"Authorization": f"Bearer {os.environ['AI_KEY']}"}
# 1. Start research
start = requests.post("https://app.aiseoworkflows.com/api/v1/research", json={"company": "acme.com"}, headers=H).json()
# 2. Poll until the brief is ready
while True:
brief = requests.get(f"https://app.aiseoworkflows.com/api/v1/accounts/{start['slug']}", headers=H).json()
if brief.get("status") != "running":
break
time.sleep(20){
"slug": "acme-com",
"status": "running",
"poll": "/api/v1/accounts/acme-com",
"note": "A research run takes a few minutes. Poll until status is ready."
}Endpoints
List accounts
Every researched account in the workspace, newest first.
/api/v1/accountshttps://app.aiseoworkflows.com/api/v1/accountsResponse
accounts[].slugstringStable id used by every other endpoint, e.g. acme-com.
accounts[].namestringCompany name.
accounts[].domainstring | nullPrimary domain.
accounts[].segmentstringThe segment the account was classified into.
accounts[].verdictobjectlabel (e.g. "Work it: strong signal", "Not a fit") and the one-line reason.
accounts[].researchedAtstringISO date of the latest research run.
accounts[].statusstringready or refreshing while a re-run is in flight.
curl https://app.aiseoworkflows.com/api/v1/accounts \
-H "Authorization: Bearer $AI_KEY"const { accounts } = await fetch("https://app.aiseoworkflows.com/api/v1/accounts", {
headers: { Authorization: `Bearer ${process.env.AI_KEY}` },
}).then((r) => r.json());accounts = requests.get(
"https://app.aiseoworkflows.com/api/v1/accounts",
headers={"Authorization": f"Bearer {os.environ['AI_KEY']}"},
).json()["accounts"]{
"accounts": [
{
"slug": "acme-com",
"name": "Acme",
"domain": "acme.com",
"segment": "B2B SaaS",
"verdict": {
"label": "Work it: strong signal",
"reason": "Hiring a Head of SEO and launching a new product line"
},
"researchedAt": "2026-10-16T09:12:44.000Z",
"status": "ready"
}
]
}Endpoints
Retrieve an account
The full brief for one account. While a first run is still in flight it answers {"status": "running"}; poll until it is ready.
/api/v1/accounts/{slug}https://app.aiseoworkflows.com/api/v1/accounts/{slug}Path parameters
slugstringrequiredThe account slug from POST /research or GET /accounts.
Response
statusstringready, refreshing or running.
verdictobjectThe fit call: label and the one-line reason.
researchobjectone_liner, facts (label, value, source_id), priorities, timeline.
signalsarrayEvidence-gated reasons to reach out: headline, strength, word-for-word quote, source_id, source_url, date, how_to_use.
rejectedSignalsarrayCandidate signals that failed the evidence gate, with the reason. Kept for transparency.
peoplearrayThe buying center: name, title, role, confidence, why, LinkedIn URL.
messagesarrayPer-contact drafts: call opener, email (subject + body), follow-up, two LinkedIn messages, plus the signal and customer story each uses.
sourcesarrayEvery source behind the brief: id, url, title, kind, date, excerpt. Facts and signals point here by source_id.
curl https://app.aiseoworkflows.com/api/v1/accounts/acme-com \
-H "Authorization: Bearer $AI_KEY"const brief = await fetch("https://app.aiseoworkflows.com/api/v1/accounts/acme-com", {
headers: { Authorization: `Bearer ${process.env.AI_KEY}` },
}).then((r) => r.json());
console.log(brief.verdict.label, brief.signals[0]?.headline);brief = requests.get(
"https://app.aiseoworkflows.com/api/v1/accounts/acme-com",
headers={"Authorization": f"Bearer {os.environ['AI_KEY']}"},
).json()
print(brief["verdict"]["label"]){
"status": "ready",
"account": {
"slug": "acme-com",
"name": "Acme",
"domain": "acme.com"
},
"verdict": {
"label": "Work it: strong signal",
"reason": "Hiring a Head of SEO and launching a new product line"
},
"researchedAt": "2026-10-16T09:12:44.000Z",
"signals": [
{
"headline": "Hiring a Head of SEO",
"strength": "strong",
"quote": "You will own organic growth across all markets.",
"source_id": "s4",
"source_url": "https://acme.com/careers/head-of-seo",
"date": "2026-10-02",
"how_to_use": "Open on the hire: the first 90 days decide the roadmap."
}
],
"people": [
{
"name": "Jordan Lee",
"title": "VP Marketing",
"role": "economic buyer",
"confidence": "high"
}
],
"messages": [
{
"contact": "Jordan Lee",
"email": {
"subject": "Your Head of SEO search",
"body": "..."
}
}
],
"sources": [
{
"id": "s4",
"url": "https://acme.com/careers/head-of-seo",
"kind": "careers",
"date": "2026-10-02"
}
]
}Endpoints
Retrieve one slice
The same data, one part at a time: small payloads that map cleanly to separate spreadsheet columns.
/api/v1/accounts/{slug}/{slice}https://app.aiseoworkflows.com/api/v1/accounts/{slug}/{slice}Path parameters
slugstringrequiredThe account slug.
slicestringrequiredresearch, signals (passed, rejected and every check), people, messages, sources, touches, chat, analyses, costs, competitors or prompts.
Response by slice
signalsobject{signals, rejected, checks}.
peopleobject{people}.
messagesobject{messages}.
analysesobject{analyses}: every analysis that has run, keyed by id, each with ranAt, costUsd, fresh and its data.
researchobject{account, verdict, researchedAt, bottomLine, research, notes}: the fact base.
sources / touches / chatobject{sources}, {touches} (logged sends) and {chat} (the stored conversation).
costsobjectDollars spent on this account: totalUsd, byStage, entries.
competitors / promptsobjectThe saved inputs for the market, gap and AI-visibility analyses.
curl https://app.aiseoworkflows.com/api/v1/accounts/acme-com/people \
-H "Authorization: Bearer $AI_KEY"const { people } = await fetch("https://app.aiseoworkflows.com/api/v1/accounts/acme-com/people", {
headers: { Authorization: `Bearer ${process.env.AI_KEY}` },
}).then((r) => r.json());people = requests.get(
"https://app.aiseoworkflows.com/api/v1/accounts/acme-com/people",
headers={"Authorization": f"Bearer {os.environ['AI_KEY']}"},
).json()["people"]{
"people": [
{
"name": "Jordan Lee",
"title": "VP Marketing",
"role": "economic buyer",
"confidence": "high",
"why": "Owns the marketing budget and the agency relationships.",
"profile_url": "https://www.linkedin.com/in/example"
}
]
}Endpoints
Run research
Start a research run on a company. It answers right away and runs in the background for a few minutes, as a job you can stop. A brief younger than 7 days is returned inline instead of spending again.
/api/v1/researchhttps://app.aiseoworkflows.com/api/v1/researchBody parameters
companystringrequiredA domain (preferred) or a company name. domain is accepted as an alias.
forcebooleanoptionalRe-run even when a brief younger than 7 days exists.
Responses
202running{slug, status, job, poll, note}: poll GET /accounts/{slug}; POST /jobs/{job}/stop stops it.
200readyA fresh brief already existed; it is returned inline with a note.
402budgetThe workspace's included research runs are used and no model key is set.
409ambiguous / busyThe name matched several companies (candidates lists them; pass the exact domain), or the company is already being researched.
curl -X POST https://app.aiseoworkflows.com/api/v1/research \
-H "Authorization: Bearer $AI_KEY" \
-H "Content-Type: application/json" \
-d '{"company": "acme.com"}'const run = await fetch("https://app.aiseoworkflows.com/api/v1/research", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.AI_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ company: "acme.com" }),
}).then((r) => r.json());run = requests.post(
"https://app.aiseoworkflows.com/api/v1/research",
json={"company": "acme.com"},
headers={"Authorization": f"Bearer {os.environ['AI_KEY']}"},
).json(){
"slug": "acme-com",
"status": "running",
"job": "job_mgxk2a7q4b9e1f",
"poll": "/api/v1/accounts/acme-com",
"note": "A research run takes a few minutes. Poll until status is ready. It shows in the app's Activity, where it can be stopped."
}Endpoints
Run an analysis
Run one deeper analysis on a researched account. Every analysis prices itself first; above $0.05 the call answers 402 with the itemized estimate and spends nothing until you repeat it with confirm: true.
/api/v1/analyses/runhttps://app.aiseoworkflows.com/api/v1/analyses/runBody parameters
slugstringrequiredThe account slug.
analysisstringrequiredOne of the analysis ids below.
paramsobjectoptionalOptional inputs, e.g. {"competitors": ["a.com", "b.com"]}, or {"prompts": [...]} for ai_visibility.
confirmbooleanoptionalRequired when the estimate exceeds $0.05. Send it only after the spend is approved.
dryRunbooleanoptionalReturn the estimate and spend nothing.
Analyses
| Id | Answers |
|---|---|
market_demand | How much search demand exists in their category, what it is worth, and where they sit in it. |
traffic_history | How their organic traffic moved against the competitive set over 24 months. |
competitor_landscape | Where they sit against the set that matters, and the sharpest gap. |
content_velocity | How much content they actually ship, and when it went quiet (also inside seo_audit). |
seo_audit | Whether they invest in SEO, and whether it is working. |
ai_visibility | Who AI assistants recommend when their buyers ask: them or competitors. |
ads_intel | Paid ads vs organic: what they pay for that organic could own, and what their ads say. |
quick_wins | Searches they almost win, and what top-3 positions would be worth. |
keyword_gap | Searches competitors win that they miss. |
Responses
202running{slug, analysis, status, job, estimatedUsd, poll}: poll GET /accounts/{slug}/analyses/{analysis} until status is ready (or failed).
200dryRunWith dryRun: true: the itemized estimate, nothing spent. POST /analyses/estimate is the same quote as its own call.
402confirmThe estimate exceeds $0.05: {error, needsConfirm, spent: 0, estimate} with itemized lines. Nothing was spent.
400setupUnknown analysis, or the analysis is not set up in this workspace.
curl -X POST https://app.aiseoworkflows.com/api/v1/analyses/run \
-H "Authorization: Bearer $AI_KEY" \
-H "Content-Type: application/json" \
-d '{"slug": "acme-com", "analysis": "quick_wins"}'
# 402 with the estimate? Approve it, then:
curl -X POST https://app.aiseoworkflows.com/api/v1/analyses/run \
-H "Authorization: Bearer $AI_KEY" \
-H "Content-Type: application/json" \
-d '{"slug": "acme-com", "analysis": "quick_wins", "confirm": true}'const run = (body) => fetch("https://app.aiseoworkflows.com/api/v1/analyses/run", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.AI_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify(body),
});
let res = await run({ slug: "acme-com", analysis: "quick_wins" });
if (res.status === 402) {
const { estimate } = await res.json();
// show estimate.totalUsd, get approval, then:
res = await run({ slug: "acme-com", analysis: "quick_wins", confirm: true });
}H = {"Authorization": f"Bearer {os.environ['AI_KEY']}"}
body = {"slug": "acme-com", "analysis": "quick_wins"}
res = requests.post("https://app.aiseoworkflows.com/api/v1/analyses/run", json=body, headers=H)
if res.status_code == 402:
print(res.json()["estimate"]["totalUsd"]) # get approval first
res = requests.post("https://app.aiseoworkflows.com/api/v1/analyses/run", json={**body, "confirm": True}, headers=H){
"error": "This analysis spends about $0.08. Repeat the call with {\"confirm\": true} to run it.",
"needsConfirm": true,
"spent": 0,
"estimate": {
"lines": [
{
"label": "Prospect's top 500 ranking keywords",
"usd": 0.061
},
{
"label": "Relevance filter + summary (app's default model)",
"usd": 0.02
}
],
"totalUsd": 0.081
}
}Endpoints
Plans: fit, outreach, call
Three jobs, each including the one before it. Start with the fit check: it costs cents per 100 companies and says which ones are worth paying for.
fit asks whether a company is a fit, in five stages, cheapest first: rules, who they are, demand, investment, why now. Only three things stop a company, each decided in code from measured facts, never by the model: it is already a customer; a hard no on who they are (not B2B, pre-product, or a stated headcount under the minimum); or no search demand (little organic traffic, no paid search, and a category measured as rarely searched). Already winning parks it as Nurture; everything else only moves its 0 to 100 score, and any stop can be continued anyway. outreach adds the buying center, signals, drafts and one analysis that proves the hook; call adds the analyses for the call. From the code's own estimators: a fit check on 100 companies costs about $0.04 (plus about $0.011 for each company that reaches Demand when search data is billed per call); outreach for the top 20 of them about $3.13 to $6.75; prepping one call about $0.44 in analyses ($0.25 when nobody in the set buys search), with AI visibility (about $3.62) only when you tick it in.
/api/v1/plans/runhttps://app.aiseoworkflows.com/api/v1/plans/runBody parameters
planstringrequiredfit, outreach or call.
slug / domains / listIdstring | arrayrequiredThe target: one account (slug or domain), up to 300 domains, or a list id. Outreach runs on up to 50 companies the fit check passed; call on one account.
thenstringoptional"outreach" with a fit check on several companies: then prepare outreach for the top 20 by fit score (top takes fewer), priced in the same quote.
continuebooleanoptionalResume past a stop on this account. Send it only when the user chose to.
includearrayoptional["ai_visibility"] on a call: tick in AI visibility, the dearest analysis.
forcebooleanoptionalRe-buy steps that are cached (every step is reused for 7 days).
confirm / dryRunbooleanoptionalThe cost gate, as everywhere. Estimates price the ceiling: every company passing every gate.
Responses
202running | queued{plan, job, status, targets, estimate, poll}: poll GET /plans/{slug} (one account) or GET /jobs/{id}. queued comes with a position when the workspace's fit checks are all busy.
200dryRunThe itemized estimate, nothing spent. POST /plans/estimate is the same quote as its own call.
402confirmAbove $0.05: {error, needsConfirm, spent: 0, estimate}. Outreach and call also answer 402 when the included research runs are used up.
409busyA plan already runs on that account, it is being researched, or none of the companies passed the fit check (outreach on several).
400bad requestUnknown plan, no target, or a call on more than one account.
| Endpoint | What it does |
|---|---|
POST/ | The itemized quote for the same body. Free. |
GET/ | One account's fit check (verdict, outcome, score, the stage it stopped at and why, every stage with its evidence and cost) and its outreach and call runs. A domain works too. |
GET/ | The fit row of every company on a list (or ?domains=a.com,b.com): verdict, score, where it stopped, the outreach and call status. full=1 returns the full state. |
POST/ | Continue anyway past a stop. Body {"plan"?} (default fit); the cost gate applies. |
POST/ | Mark as fit: overrides a stop or a park, so outreach can run. |
Fit check fields
fit.verdictstringfit, nurture, not_a_fit or customer.
fit.outcomestringpassed, stopped or parked; stoppedAt and reason say where and why, in words a rep can read.
fit.scorenumber | null0 to 100 (ICP 40, need 35, timing 25, minus an open deal or a recent touch). partial when some lines could not be checked yet; a stopped company has none.
fit.stagesarrayEach stage's status (done, stopped, parked, skipped), cost, cached and its gate's evidence.
curl -X POST https://app.aiseoworkflows.com/api/v1/plans/run \
-H "Authorization: Bearer $AI_KEY" \
-H "Content-Type: application/json" \
-d '{"plan": "fit", "listId": "3f9a1c2e"}'const res = await fetch("https://app.aiseoworkflows.com/api/v1/plans/run", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.AI_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({"plan": "fit", "listId": "3f9a1c2e"}),
}).then((r) => r.json());res = requests.post(
"https://app.aiseoworkflows.com/api/v1/plans/run",
json={"plan": "fit", "listId": "3f9a1c2e"},
headers={"Authorization": f"Bearer {os.environ['AI_KEY']}"},
).json(){
"plan": "fit",
"job": "job_mgxk2a7q4b9e1f",
"status": "running",
"targets": 48,
"estimate": {
"plan": "fit",
"companies": 48,
"alreadyChecked": 0,
"lines": [
{
"stage": "rules",
"label": "Rules: customers, CRM status, competitor of a client",
"usd": 0
},
{
"stage": "icp",
"label": "Who they are: one model read per 10 companies",
"usd": 0.02
},
{
"stage": "demand",
"label": "Demand: search footprint for up to 48 companies on the subscription's unit pool",
"usd": 0
},
{
"stage": "invest",
"label": "Investment: sitemap and blog feed (plain fetches)",
"usd": 0
},
{
"stage": "timing",
"label": "Why now: public job boards, SEC filings, news",
"usd": 0
}
],
"totalUsd": 0.02,
"perCompanyUsd": 0.0004,
"ceiling": true,
"note": "Prices the ceiling: every company passing every gate. Stages after a stop never run, so the real spend is lower."
},
"poll": {
"job": "/api/v1/jobs/job_mgxk2a7q4b9e1f",
"plans": "/api/v1/plans?list=3f9a1c2e"
}
}Endpoints
Work an account
Everything a rep does on an open account: ask about it, rewrite the drafts, reject a draft with a reason, log a send, find more people and their emails, or delete it.
| Endpoint | What it does |
|---|---|
POST/ | Answer a question from the saved research (no new research). Body {"question"}. |
POST/ | Rewrite the outreach drafts. Body {"instruction", "contact"?}; with contact only that person's drafts change. Returns the new messages. |
POST/ | Reject a contact's draft with a reason; the next drafts read it. |
POST/ | Log a send ({"contact", "channel"}); nothing is sent by the app. |
POST/ | Find more contacts at the company in the people database. |
POST/ | Validated email for one buying-center person (index from /people). |
DELETE/ | Delete the account and everything saved with it. 409 while a run is in flight. |
402 with the estimate; dryRun: true always just quotes.curl -X POST https://app.aiseoworkflows.com/api/v1/accounts/acme-com/ask \
-H "Authorization: Bearer $AI_KEY" \
-H "Content-Type: application/json" \
-d '{"question": "Who should we contact first, and with which signal?"}'const res = await fetch("https://app.aiseoworkflows.com/api/v1/accounts/acme-com/ask", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.AI_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({"question": "Who should we contact first, and with which signal?"}),
}).then((r) => r.json());res = requests.post(
"https://app.aiseoworkflows.com/api/v1/accounts/acme-com/ask",
json={"question": "Who should we contact first, and with which signal?"},
headers={"Authorization": f"Bearer {os.environ['AI_KEY']}"},
).json(){
"slug": "acme-com",
"question": "Who should we contact first, and with which signal?",
"answer": "Start with Jordan Lee (VP Marketing): the Head of SEO opening is theirs to fill, so open on that hire."
}Endpoints
Analyses and their inputs
List what can run, quote it, run it, read one result. The competitive set and the buyer prompts are the inputs most analyses read; set them before running.
| Endpoint | What it does |
|---|---|
GET/ | Every analysis id with its question and whether it is set up in this workspace. |
POST/ | The itemized quote for {"slug", "analysis", "params"?}. Free. |
GET/ | One result with status: ready, running, not_run or failed. |
GET PUT/ | Read or replace the competitive set (bare domains, up to 10). |
POST/ | A proposed change {keep, add, remove} with a reason per row. Never saved: PUT what you accept. |
GET PUT/ | Read or replace the AI-visibility buyer prompts (up to 25). |
POST/ | Twelve suggested buyer prompts. Never saved. |
curl -X PUT https://app.aiseoworkflows.com/api/v1/accounts/acme-com/competitors \
-H "Authorization: Bearer $AI_KEY" \
-H "Content-Type: application/json" \
-d '{"competitors": ["rival-one.com", "rival-two.com"]}'const res = await fetch("https://app.aiseoworkflows.com/api/v1/accounts/acme-com/competitors", {
method: "PUT",
headers: {
Authorization: `Bearer ${process.env.AI_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({"competitors": ["rival-one.com", "rival-two.com"]}),
}).then((r) => r.json());res = requests.put(
"https://app.aiseoworkflows.com/api/v1/accounts/acme-com/competitors",
json={"competitors": ["rival-one.com", "rival-two.com"]},
headers={"Authorization": f"Bearer {os.environ['AI_KEY']}"},
).json(){
"competitors": [
"rival-one.com",
"rival-two.com"
]
}Endpoints
Lists and finding companies
Named company lists per workspace, joined to research, plus a finder that proposes real companies for a plain-language description.
| Endpoint | What it does |
|---|---|
GET POST/ | All lists, or create one: {"name", "domains"} (normalized, deduped, up to 200). |
GET DELETE/ | One list with each member's research status and verdict, or delete it. |
POST/ | Add {"domains"}. |
DELETE/ | Remove one domain. |
POST/ | More real companies like the members (optional description). |
POST/ | Real companies matching {"description"}, judged against the ICP; already researched ones carry their slug. |
curl -X POST https://app.aiseoworkflows.com/api/v1/lists \
-H "Authorization: Bearer $AI_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "Q4 payments", "domains": ["acme.com", "beta.io"]}'const res = await fetch("https://app.aiseoworkflows.com/api/v1/lists", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.AI_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({"name": "Q4 payments", "domains": ["acme.com", "beta.io"]}),
}).then((r) => r.json());res = requests.post(
"https://app.aiseoworkflows.com/api/v1/lists",
json={"name": "Q4 payments", "domains": ["acme.com", "beta.io"]},
headers={"Authorization": f"Bearer {os.environ['AI_KEY']}"},
).json(){
"ok": true,
"list": {
"id": "3f9a1c2e",
"name": "Q4 payments",
"createdAt": "2026-10-17T10:00:00.000Z",
"domains": [
"acme.com",
"beta.io"
]
}
}Endpoints
Qualify companies
Screen up to 300 companies against the workspace ICP and customer base before spending on research: fit, maybe, not_a_fit or customer, each with a one-line reason. Verdicts are cached for 7 days.
Body parameters
rowsarrayoptional[{"domain", "name", "extra"}]: at least a domain or a name per row.
domainsarrayoptionalOr just domains.
csvstringoptionalOr CSV text with a header row; the website and name columns are detected (override with domainColumn / nameColumn).
confirm / dryRunbooleanoptionalThe cost gate, as everywhere.
| Endpoint | What it does |
|---|---|
POST/ | The quote for the screen. Free. |
POST/ | Up to 20 rows answer inline; larger screens answer 202 with a job to poll. |
curl -X POST https://app.aiseoworkflows.com/api/v1/qualify \
-H "Authorization: Bearer $AI_KEY" \
-H "Content-Type: application/json" \
-d '{"domains": ["acme.com", "beta.io"]}'const res = await fetch("https://app.aiseoworkflows.com/api/v1/qualify", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.AI_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({"domains": ["acme.com", "beta.io"]}),
}).then((r) => r.json());res = requests.post(
"https://app.aiseoworkflows.com/api/v1/qualify",
json={"domains": ["acme.com", "beta.io"]},
headers={"Authorization": f"Bearer {os.environ['AI_KEY']}"},
).json(){
"results": [
{
"domain": "acme.com",
"name": "",
"verdict": "fit",
"reason": "B2B payments platform selling to mid-market merchants in the EU",
"lookalike": null,
"cached": false
}
],
"counts": {
"fit": 1,
"maybe": 1,
"not_a_fit": 0,
"customer": 0
}
}Endpoints
Plays
Packaged motions: source companies, screen them, save the fits as a list, research the best ones and write a play-specific opener. Run the whole play as one job, or call the steps yourself.
| Endpoint | What it does |
|---|---|
GET/ | Every play with its input and steps. |
GET/ | One play and its recent runs. |
GET/ | Run history (last 5). |
POST/ | Itemized quote for the whole play. Free. |
POST/ | The whole play as a job: {"input", "confirm"} → 202 with a job id to poll or stop (see Jobs). |
POST/ | Step 1 only: the sourced companies. |
POST/ | Step 4 only: the opener for one researched account ({"slug", "input"}). |
curl -X POST https://app.aiseoworkflows.com/api/v1/plays/investor_sweep/run \
-H "Authorization: Bearer $AI_KEY" \
-H "Content-Type: application/json" \
-d '{"input": "Seedcamp", "confirm": true}'const res = await fetch("https://app.aiseoworkflows.com/api/v1/plays/investor_sweep/run", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.AI_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({"input": "Seedcamp", "confirm": true}),
}).then((r) => r.json());res = requests.post(
"https://app.aiseoworkflows.com/api/v1/plays/investor_sweep/run",
json={"input": "Seedcamp", "confirm": True},
headers={"Authorization": f"Bearer {os.environ['AI_KEY']}"},
).json(){
"job": "job_mgxk3c9d1e2f3a",
"status": "running",
"poll": "/api/v1/jobs/job_mgxk3c9d1e2f3a",
"estimatedUsd": 0.267
}Endpoints
Jobs: poll, queue, stop
Every run started over the API or MCP is a job: research, analyses, plans, plays and screens over 20 rows. It has an id, a place in the workspace's queue, a Stop, and a row in the app's Activity that names the key that started it, so a teammate can follow or stop it there too.
| Endpoint | What it does |
|---|---|
GET/ | status, step N of M, the current stage, stages, costUsd, summary and, for plays and screens, result. |
POST/ | Stop it now, with anything it started (a list's outreach stops every account in it). 200 {ok, job, stoppedChildren, note}; 404 for an unknown id; 409 when it already ended. |
Statuses
| Status | Meaning |
|---|---|
queued | Waiting for a free slot; position is its place in line. It starts by itself, first in, first out. |
running | Working; stages fill in as each one lands. |
done | Finished. Plays and screens carry result; research, analyses and plans saved theirs on the account. |
failed | error says why. |
stopped | Stopped from the API, MCP or the app. Nothing else runs or spends; whatever finished before the stop is kept, and a stopped run never replaces a good result with a partial one. |
stalled | No update for 12 minutes (the server restarted mid-run). |
Caps per workspace
How many jobs of one kind run at once. The count is shared with runs started in the app; past a cap a job is queued, not refused.
| Kind | At once |
|---|---|
| Fit checks | 3 |
| Research runs, and one account's outreach or call | 4 |
| Analyses | 6 |
| Plays | 2 |
| Qualify screens over 20 rows | 2 |
A list's outreach is one job that holds a run per account; those count as research and go three at a time. One account never runs two research jobs at once: a second start answers 409.
# Poll
curl https://app.aiseoworkflows.com/api/v1/jobs/job_mgxk2a7q4b9e1f \
-H "Authorization: Bearer $AI_KEY"
# Stop it, and anything it started
curl -X POST https://app.aiseoworkflows.com/api/v1/jobs/job_mgxk2a7q4b9e1f/stop \
-H "Authorization: Bearer $AI_KEY"const headers = { Authorization: `Bearer ${process.env.AI_KEY}` };
const job = await fetch("https://app.aiseoworkflows.com/api/v1/jobs/job_mgxk2a7q4b9e1f", { headers }).then((r) => r.json());
// Stop it, and anything it started
await fetch("https://app.aiseoworkflows.com/api/v1/jobs/job_mgxk2a7q4b9e1f/stop", { method: "POST", headers });H = {"Authorization": f"Bearer {os.environ['AI_KEY']}"}
job = requests.get("https://app.aiseoworkflows.com/api/v1/jobs/job_mgxk2a7q4b9e1f", headers=H).json()
# Stop it, and anything it started
requests.post("https://app.aiseoworkflows.com/api/v1/jobs/job_mgxk2a7q4b9e1f/stop", headers=H){
"id": "job_mgxk2a7q4b9e1f",
"kind": "plan",
"status": "running",
"title": "Check fit · 48 companies from Q4 payments",
"step": 3,
"steps": 5,
"current": "demand",
"stages": [
{
"id": "rules",
"status": "done",
"label": "Rules: 47 of 48 passed"
},
{
"id": "icp",
"status": "done",
"label": "Who they are: 41 of 47 passed"
},
{
"id": "demand",
"status": "running",
"label": "Demand: 18 of 41"
}
],
"costUsd": null,
"summary": null,
"startedAt": "2026-10-19T10:02:11.000Z",
"endedAt": null
}Endpoints
Workspace, positioning, signals, costs
The settings automation needs: what is set up, what the workspace sells and to whom, which signals it watches, and what it has spent.
| Endpoint | What it does |
|---|---|
GET/ | Name, included runs left, capabilities, research cost per run, the key's limits. |
GET PATCH/ | The positioning (one-liner, offerings, claims, customer stories, segments, why-we-win) plus the customer list with results. PATCH any of those keys (or customers, the full list); the previous version is kept. |
POST/ | {"what": "why" | "segments"}: suggested items, never saved. |
GET/ | Built-in signals, the signal library and this workspace's selection. |
PUT/ | {"builtin"?, "library"?}: a key you leave out keeps its current value. |
GET/ | Dollars spent across the workspace, by stage and by account. |
GET/ | What one research run costs here. |
curl https://app.aiseoworkflows.com/api/v1/workspace \
-H "Authorization: Bearer $AI_KEY"const res = await fetch("https://app.aiseoworkflows.com/api/v1/workspace", {
headers: { Authorization: `Bearer ${process.env.AI_KEY}` },
}).then((r) => r.json());res = requests.get(
"https://app.aiseoworkflows.com/api/v1/workspace",
headers={"Authorization": f"Bearer {os.environ['AI_KEY']}"},
).json(){
"name": "Acme Sales",
"trial": {
"left": 3,
"total": 5
},
"capabilities": {
"model": true,
"webSearch": true,
"peopleDatabase": false,
"analyses": [
{
"id": "market_demand",
"setUp": true
}
]
},
"research": {
"perRunUsd": 0.05,
"freshDays": 7
},
"limits": {
"requestsPerMinute": 60,
"confirmAboveUsd": 0.05
}
}MCP
Connect MCP
Give your AI agent the same account research as a tool. Account Intel runs a remote MCP server, so there is nothing to install: add the URL and your API key to the client you already use.
https://app.aiseoworkflows.com/mcp- Step 1Create an API key
In the app under Settings → API & MCP. Name it after the tool it is for; the full key is shown once. The same page has these steps with your key already filled in, and turns to Connected on the key's first call. MCP uses the same keys, workspace and rate limit as the REST API.
Get an API key - Step 2Connect your tool
- Run this once in your terminal.
--scope usermakes it available in every project; leave it out to add it to the current project only.claude mcp add --transport http --scope user account-intel https://app.aiseoworkflows.com/mcp --header "Authorization: Bearer ai_live_YOUR_KEY" - Check that it connected. Run
claude mcp list(account-intel shows Connected), or type/mcpinside Claude Code. - Ask it something. Claude Code asks before it uses one of the tools for the first time.
Not working? Quit and reopen the assistant first (most read their config only at start), then check in Settings → API & MCP that the key is not revoked.
Claude's Connectors screen (in the desktop app and on claude.ai) only adds servers that sign in with OAuth, so it cannot hold an API key yet. Claude Desktop connects through a small bridge on your computer instead (
mcp-remote), which adds the key to every call.- Have Node.js 18 or newer. The bridge runs with npx. Check with
node -v; if it is missing, install it from nodejs.org. - Open the config file. In Claude Desktop: Settings → Developer → Edit Config. It opens
claude_desktop_config.json(Mac: ~/Library/Application Support/Claude/, Windows: %APPDATA%\Claude\). - Paste this and save. If the file already has
mcpServers, add only the account-intel entry inside it. The key sits inAUTH_HEADER, not in the args, because some clients do not escape spaces inside args.{ "mcpServers": { "account-intel": { "command": "npx", "args": [ "-y", "mcp-remote", "https://app.aiseoworkflows.com/mcp", "--header", "Authorization:${AUTH_HEADER}" ], "env": { "AUTH_HEADER": "Bearer ai_live_YOUR_KEY" } } } } - Quit Claude Desktop completely and open it again. Account Intel shows in the tools menu under the chat box.
Not working? Quit and reopen the assistant first (most read their config only at start), then check in Settings → API & MCP that the key is not revoked.
- Open Cursor Settings → Tools & MCP → New MCP server. Cursor opens
~/.cursor/mcp.json. To add it to one project only, use.cursor/mcp.jsonin that project. - Paste this and save.
{ "mcpServers": { "account-intel": { "url": "https://app.aiseoworkflows.com/mcp", "headers": { "Authorization": "Bearer ai_live_YOUR_KEY" } } } } - Check the dot. account-intel turns green in the MCP list, and its tools work in Agent chat.
Not working? Quit and reopen the assistant first (most read their config only at start), then check in Settings → API & MCP that the key is not revoked.
- Open the Command Palette and run MCP: Open User Configuration. Or create
.vscode/mcp.jsonin a project. That file holds your key, so keep it out of git. - Paste this and save.
{ "servers": { "account-intel": { "type": "http", "url": "https://app.aiseoworkflows.com/mcp", "headers": { "Authorization": "Bearer ai_live_YOUR_KEY" } } } } - Click Start above the server, then open Copilot Chat in Agent mode. The Account Intel tools are in the tools picker.
Not working? Quit and reopen the assistant first (most read their config only at start), then check in Settings → API & MCP that the key is not revoked.
Not available yet. ChatGPT cannot connect to your workspace yet: its custom connectors sign in with OAuth or with no sign-in at all, so they cannot send an API key, and Account Intel's MCP server signs in with API keys. Use Claude Code, Claude Desktop, Cursor or VS Code today: the same tools are there.- Clients that connect to remote servers (streamable HTTP) take the address and one header.
https://app.aiseoworkflows.com/mcpAuthorization: Bearer ai_live_YOUR_KEY - Clients that only start local servers (a command) use the bridge. Needs Node.js 18 or newer.
{ "mcpServers": { "account-intel": { "command": "npx", "args": [ "-y", "mcp-remote", "https://app.aiseoworkflows.com/mcp", "--header", "Authorization:${AUTH_HEADER}" ], "env": { "AUTH_HEADER": "Bearer ai_live_YOUR_KEY" } } } }
Not working? Quit and reopen the assistant first (most read their config only at start), then check in Settings → API & MCP that the key is not revoked.
- Run this once in your terminal.
- Step 3Ask your agent
Try a request like one of these; the agent picks the tools.
Check fit on my list Q4 payments and tell me which companies passed and why the others stopped.Research acme.com, then tell me who to contact first and why, using only signals that have a source. - Step 4Approve before it spends
Reads are free. Research uses one of the workspace's included runs. Any tool call above $0.05 (analyses, plans, plays, large screens, draft rewrites) comes back as
"status": "needs_confirmation"withspent: 0: the agent has to show you the price and call the same tool again withconfirm: true. Nothing spends silently. Every run it starts is a job:get_jobfollows it,stop_jobstops it at once, and it shows in the app's Activity, where you can stop it too.
MCP
Tools
What tools/list returns. Each tool has a JSON Schema for its inputs and answers with both readable text and structuredContent.
| Tool | Inputs | What it does |
|---|---|---|
list_accounts | none | List every researched account with its slug, domain, fit verdict and research date. |
get_account | account, part? | One account's brief summary by slug or domain; part returns one slice in full (research, signals, people, messages, sources, touches, chat, analyses, costs, competitors, prompts). |
research_account | domain, force? | Start a research run (uses one included run). Returns the saved brief instead when one younger than 7 days exists. |
ask_account | account, question, confirm? | Answer a question from the account's saved research. |
revise_drafts | account, instruction, contact?, confirm? | Rewrite the outreach drafts, optionally for one contact. |
log_touch | account, contact, channel? | Record that a draft was sent (nothing is sent by the app). |
list_analyses | slug | The analyses that can run on an account, and the saved results of those that already ran. |
get_analysis | account, analysis | One analysis result with its status. |
run_analysis | slug, analysis, params?, confirm?, estimate_only? | Run one analysis. Above $0.05 it only returns the estimate until called again with confirm: true. |
manage_competitors | account, action, competitors?, instruction? | get, set or suggest the competitive set. Suggestions are never saved. |
find_companies | description | Real companies matching a description, against the ICP. |
qualify_companies | companies, confirm? | ICP fit screen for up to 300 companies; over 20 returns a job. |
manage_lists | action, list?, name?, domains? | list, get, create, add, remove or delete company lists. |
list_plays | play? | The plays, or one play with its runs and estimate. |
run_play | play, input?, confirm? | Run a whole play as a background job. |
run_plan | plan, account? | domains? | list?, then_outreach?, continue_anyway?, include_ai_visibility?, confirm?, estimate_only? | Run one of the three jobs as a background job: fit (the fit waterfall, a 0-100 score, where it stopped and why), outreach (fit, brief, drafts and one proof analysis) or call (outreach and the call's analyses). Quotes first above $0.05. |
get_plan | account | An account's fit check and its outreach and call runs. |
get_job | job | Status, progress and result of a background job (plans, research, analyses, plays, large qualify screens). |
stop_job | job | Stop a background job now; whatever finished before the stop is kept. |
get_context | none | The workspace positioning and its signal selection. |
get_costs | account? | Dollars spent by the workspace or one account. |
Protocol
Streamable HTTP transport, protocol version 2025-06-18, JSON-RPC 2.0 over POST /mcp with JSON responses (no event stream). Supported methods: initialize, ping, tools/list, tools/call. Unknown methods return -32601, bad arguments -32602; business errors (unknown account, analysis not set up) come back as tool results with isError: true. A missing or invalid key gets HTTP 401. initialize also hands the agent its rules: never confirm a spend without the user's approval, and start with run_plan (fit), the cheap check that says whether anything else is worth paying for. Tools that start work return a job id: follow it with get_job (or get_plan for one account) and stop it with stop_job.
curl -X POST https://app.aiseoworkflows.com/mcp \
-H "Authorization: Bearer $AI_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call",
"params":{"name":"get_account","arguments":{"account":"acme.com"}}}'const res = await fetch("https://app.aiseoworkflows.com/mcp", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.AI_KEY}`,
"Content-Type": "application/json",
Accept: "application/json, text/event-stream",
},
body: JSON.stringify({
jsonrpc: "2.0", id: 1, method: "tools/call",
params: { name: "get_account", arguments: { account: "acme.com" } },
}),
});
const { result } = await res.json();
console.log(result.structuredContent.verdict);res = requests.post(
"https://app.aiseoworkflows.com/mcp",
headers={
"Authorization": f"Bearer {os.environ['AI_KEY']}",
"Accept": "application/json, text/event-stream",
},
json={
"jsonrpc": "2.0", "id": 1, "method": "tools/call",
"params": {"name": "get_account", "arguments": {"account": "acme.com"}},
},
)
print(res.json()["result"]["structuredContent"]["verdict"]){
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [
{
"type": "text",
"text": "{ \"slug\": \"acme-com\", ... }"
}
],
"structuredContent": {
"slug": "acme-com",
"name": "Acme",
"status": "ready",
"verdict": {
"label": "Work it: strong signal",
"reason": "Hiring a Head of SEO and launching a new product line"
},
"signals": [
"..."
],
"people": [
"..."
],
"drafts": [
"..."
]
},
"isError": false
}
}Guides
Use it from Clay
Qualify a Clay table with the cheap fit check, research only the companies that fit, and pull the signal, the buyer and the drafted email into columns. Every step is one HTTP API column: no code, no webhooks.
1Create a key for Clay1 minute · free
In the app: Settings → API & MCP → New key, name it Clay, copy it (it is shown once). Revoke it there any time without touching your other keys. See Authentication.
2Add the fit check columncents per company · runs in seconds
- In your Clay table: + Add column → Add enrichment, search HTTP API.
- Method POST, Endpoint
https://app.aiseoworkflows.com/api/v1/plans/run. - Headers:
Authorization=Bearer ai_live_YOUR_KEY,Content-Type=application/json. - Body: type
/where the domain goes and pick your Domain column (Clay inserts it as a token). - Save and run 10 rows first. The check answers right away with a job; the verdict lands a few seconds later (step 3).
One company's fit check costs well under $0.05, so it simply runs. Sending many domains in ONE call? Add "dryRun": true to see the quote, then "confirm": true (Spending).
{"plan": "fit", "domains": ["/Domain"]}https://app.aiseoworkflows.com/api/v1/plans/runBearer ai_live_••••Content-Typeapplication/json{ "plan": "fit", "domains": [ / Domain ] }3Read the verdictfree · a GET
- Add another HTTP API column: Method GET, Endpoint
https://app.aiseoworkflows.com/api/v1/plans/followed by the/ Domaintoken (a domain works in place of the slug). - Click a filled cell and add these as columns:
fit.verdict(fit,nurture,not_a_fit,customer),fit.score(0 to 100),fit.reasonandfit.stoppedAt. - Rows still checking say so: filter them and run the column again a minute later.
GET https://app.aiseoworkflows.com/api/v1/plans/{Domain}4Research only the fitsa few minutes per company
- Add an HTTP API column: Method POST, Endpoint
https://app.aiseoworkflows.com/api/v1/research, same two headers. - Body
{"company": "/Domain"}. - In the column's run settings, Only run if the verdict column equals
fit: stopped and parked companies never pay for research. - Add the
slugfrom the response as a column; step 5 uses it.
{"company": "/Domain"}5Pull the signal, the buyer and the draftfree · a GET
- Add an HTTP API column: Method GET, Endpoint
https://app.aiseoworkflows.com/api/v1/accounts/followed by the/ slugtoken. - Run it about five minutes after step 4. Rows whose
statusis stillrunning: filter and run again. - Add as columns:
verdict.label,signals[0].headline,signals[0].source_url,people[0].name,people[0].title,messages[0].email.subject,messages[0].email.body. - Lighter columns:
/accounts/{slug}/signals,/peopleor/messagesreturn one slice each.
GET https://app.aiseoworkflows.com/api/v1/accounts/{slug}6Costs, limits and stopping
- Each key allows 60 requests per minute; over the limit Clay gets
429withRetry-After. Keep Clay's column rate at or under 60 per minute. - Every run shows in the app's Activity with its steps and cost, and lands in the workspace's cost ledger. Stop any run there, or with
POST /jobs/{id}/stop. - Everything is cached for 7 days: re-running a row reuses what was already bought.
Guides
How it works
One research run: resolve the company → collect public sources (site, news, careers, filings) → extract the fact base and the buying center → hunt signals, where a signal only counts with a word-for-word quote, a source and a date (the rest is filed as rejected, also in the API) → match a customer story → draft the outreach, one signal per person.
Deeper analyses (market demand, paid ads vs organic, AI visibility, keyword gap and the rest) run on demand, each stating its exact cost first and keeping its result with the account. Every claim keeps its source_id; every cent lands in the workspace's cost ledger.
A fit check can run first and decide whether a company is worth researching at all (see Plans).
Guides
Spending, confirm and jobs
One contract for every call that can cost money.
- Quote. Any call that can spend accepts
dryRun: true: it answers200with{dryRun, spent: 0, estimate}and does nothing else. The estimate is itemized (lines[]oflabel+usd, thentotalUsd). - Confirm. Above $0.05 a call without
confirm: trueanswers402with the same estimate,needsConfirm: trueandspent: 0. Under $0.05 it simply runs. - Poll. Long work answers
202with ajobid: research and analyses are polled on the account (GET /accounts/{slug},GET /accounts/{slug}/analyses/{analysis}), plans atGET /plans/{slug}, plays and screens over 20 rows atGET /jobs/{id}. - Stop.
POST /jobs/{id}/stopstops any of them at once. Nothing else spends, and what finished before the stop is kept.
Every cent lands in the workspace ledger, readable at GET /costs and GET /accounts/{slug}/costs.
{
"error": "This play spends about $0.27. Repeat the call with {\"confirm\": true} to run it.",
"needsConfirm": true,
"spent": 0,
"estimate": {
"lines": [
{
"label": "source: page fetch free; model fallback only for script-rendered pages",
"step": "source",
"usd": 0.004
},
{
"label": "screen: assumes ~60 companies, model-only",
"step": "screen",
"usd": 0.014
},
{
"label": "research: up to 5 accounts × ~$0.050 each",
"step": "research",
"usd": 0.25
}
],
"totalUsd": 0.267
}
}Guides
Errors & limits
Errors are JSON with a single readable error field, plus context where it helps (estimate, candidates).
| Status | Meaning |
|---|---|
400 | Bad request: a missing field, a body that is not a JSON object, an unknown analysis, or something not set up in this workspace (no model, no people database). |
401 | Missing or invalid key. Create one under Settings → API & MCP. |
402 | Spend needs confirmation (confirm: true), or the workspace's included research runs are used up. |
404 | Unknown account, slice, list, play, plan, job or endpoint. |
409 | Ambiguous company name (the response lists candidates; pass the exact domain), a run already in flight on that account (research, a plan, or deleting it), or stopping a job that already ended. |
429 | Over 60 requests per minute on this key; honor Retry-After. |
500 | Server error; the body carries the message. Safe to retry reads. |
Machine-readable spec: openapi.json.
{
"error": "Missing or invalid API key. Pass Authorization: Bearer ai_live_…; keys are created in the app under Settings → API & MCP. Docs: https://aiseoworkflows.com/developers"
}