API key
ProductOpenAPILog inGet an API key
New · Fit check and plans

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.

Base URL
https://app.aiseoworkflows.com/api/v1

All 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.

Honesty contract, same as the app. A signal only exists in a response if it carried a word-for-word quote, a source and a date past the evidence gate. Analyses state their cost before they spend, and neither the API nor the MCP server spends more than $0.05 without an explicit confirm: true.
Response200
{
  "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_KEY

Name 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"

Get started

Quickstart

Research is asynchronous: start a run, then poll the account until its status is ready. A run takes a few minutes.

  1. Start research. POST /research answers immediately with the account slug.
  2. Poll the account. GET /accounts/{slug} returns "status": "running" until the brief exists, then the full brief.
  3. Go deeper when it matters. Run an analysis with POST /analyses/run; anything above $0.05 asks for confirm: true first.
Working a list? Start with a fit check (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"
Response202
{
  "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.

GET
/api/v1/accounts

Response

accounts[].slugstring

Stable id used by every other endpoint, e.g. acme-com.

accounts[].namestring

Company name.

accounts[].domainstring | null

Primary domain.

accounts[].segmentstring

The segment the account was classified into.

accounts[].verdictobject

label (e.g. "Work it: strong signal", "Not a fit") and the one-line reason.

accounts[].researchedAtstring

ISO date of the latest research run.

accounts[].statusstring

ready or refreshing while a re-run is in flight.

curl https://app.aiseoworkflows.com/api/v1/accounts \
  -H "Authorization: Bearer $AI_KEY"
Response200
{
  "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.

GET
/api/v1/accounts/{slug}

Path parameters

slugstringrequired

The account slug from POST /research or GET /accounts.

Response

statusstring

ready, refreshing or running.

verdictobject

The fit call: label and the one-line reason.

researchobject

one_liner, facts (label, value, source_id), priorities, timeline.

signalsarray

Evidence-gated reasons to reach out: headline, strength, word-for-word quote, source_id, source_url, date, how_to_use.

rejectedSignalsarray

Candidate signals that failed the evidence gate, with the reason. Kept for transparency.

peoplearray

The buying center: name, title, role, confidence, why, LinkedIn URL.

messagesarray

Per-contact drafts: call opener, email (subject + body), follow-up, two LinkedIn messages, plus the signal and customer story each uses.

sourcesarray

Every 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"
Response200
{
  "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.

GET
/api/v1/accounts/{slug}/{slice}

Path parameters

slugstringrequired

The account slug.

slicestringrequired

research, 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).

costsobject

Dollars spent on this account: totalUsd, byStage, entries.

competitors / promptsobject

The 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"
Response200
{
  "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.

POST
/api/v1/research

Body parameters

companystringrequired

A domain (preferred) or a company name. domain is accepted as an alias.

forcebooleanoptional

Re-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.

200ready

A fresh brief already existed; it is returned inline with a note.

402budget

The workspace's included research runs are used and no model key is set.

409ambiguous / busy

The 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"}'
Response202
{
  "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.

POST
/api/v1/analyses/run

Body parameters

slugstringrequired

The account slug.

analysisstringrequired

One of the analysis ids below.

paramsobjectoptional

Optional inputs, e.g. {"competitors": ["a.com", "b.com"]}, or {"prompts": [...]} for ai_visibility.

confirmbooleanoptional

Required when the estimate exceeds $0.05. Send it only after the spend is approved.

dryRunbooleanoptional

Return the estimate and spend nothing.

Analyses

IdAnswers
market_demandHow much search demand exists in their category, what it is worth, and where they sit in it.
traffic_historyHow their organic traffic moved against the competitive set over 24 months.
competitor_landscapeWhere they sit against the set that matters, and the sharpest gap.
content_velocityHow much content they actually ship, and when it went quiet (also inside seo_audit).
seo_auditWhether they invest in SEO, and whether it is working.
ai_visibilityWho AI assistants recommend when their buyers ask: them or competitors.
ads_intelPaid ads vs organic: what they pay for that organic could own, and what their ads say.
quick_winsSearches they almost win, and what top-3 positions would be worth.
keyword_gapSearches 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).

200dryRun

With dryRun: true: the itemized estimate, nothing spent. POST /analyses/estimate is the same quote as its own call.

402confirm

The estimate exceeds $0.05: {error, needsConfirm, spent: 0, estimate} with itemized lines. Nothing was spent.

400setup

Unknown 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}'
Response402
{
  "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.

POST
/api/v1/plans/run

Body parameters

planstringrequired

fit, outreach or call.

slug / domains / listIdstring | arrayrequired

The 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.

continuebooleanoptional

Resume 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.

forcebooleanoptional

Re-buy steps that are cached (every step is reused for 7 days).

confirm / dryRunbooleanoptional

The 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.

200dryRun

The itemized estimate, nothing spent. POST /plans/estimate is the same quote as its own call.

402confirm

Above $0.05: {error, needsConfirm, spent: 0, estimate}. Outreach and call also answer 402 when the included research runs are used up.

409busy

A plan already runs on that account, it is being researched, or none of the companies passed the fit check (outreach on several).

400bad request

Unknown plan, no target, or a call on more than one account.

EndpointWhat it does
POST/plans/estimateThe itemized quote for the same body. Free.
GET/plans/{slug}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/plans?list={id}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/plans/{slug}/continueContinue anyway past a stop. Body {"plan"?} (default fit); the cost gate applies.
POST/plans/{slug}/markMark as fit: overrides a stop or a park, so outreach can run.

Fit check fields

fit.verdictstring

fit, nurture, not_a_fit or customer.

fit.outcomestring

passed, stopped or parked; stoppedAt and reason say where and why, in words a rep can read.

fit.scorenumber | null

0 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.stagesarray

Each 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"}'
Response202
{
  "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.

EndpointWhat it does
POST/accounts/{slug}/askAnswer a question from the saved research (no new research). Body {"question"}.
POST/accounts/{slug}/messages/reviseRewrite the outreach drafts. Body {"instruction", "contact"?}; with contact only that person's drafts change. Returns the new messages.
POST/accounts/{slug}/messages/feedbackReject a contact's draft with a reason; the next drafts read it.
POST/accounts/{slug}/touchesLog a send ({"contact", "channel"}); nothing is sent by the app.
POST/accounts/{slug}/people/searchFind more contacts at the company in the people database.
POST/accounts/{slug}/people/{index}/emailValidated email for one buying-center person (index from /people).
DELETE/accounts/{slug}Delete the account and everything saved with it. 409 while a run is in flight.
Quoted before it spends. Ask and revise are model calls priced on the workspace model. Above $0.05 they answer 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?"}'
Response200
{
  "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.

EndpointWhat it does
GET/analysesEvery analysis id with its question and whether it is set up in this workspace.
POST/analyses/estimateThe itemized quote for {"slug", "analysis", "params"?}. Free.
GET/accounts/{slug}/analyses/{analysis}One result with status: ready, running, not_run or failed.
GET PUT/accounts/{slug}/competitorsRead or replace the competitive set (bare domains, up to 10).
POST/accounts/{slug}/competitors/suggestA proposed change {keep, add, remove} with a reason per row. Never saved: PUT what you accept.
GET PUT/accounts/{slug}/promptsRead or replace the AI-visibility buyer prompts (up to 25).
POST/accounts/{slug}/prompts/suggestTwelve 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"]}'
Response200
{
  "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.

EndpointWhat it does
GET POST/listsAll lists, or create one: {"name", "domains"} (normalized, deduped, up to 200).
GET DELETE/lists/{id}One list with each member's research status and verdict, or delete it.
POST/lists/{id}/membersAdd {"domains"}.
DELETE/lists/{id}/members/{domain}Remove one domain.
POST/lists/{id}/suggestMore real companies like the members (optional description).
POST/companies/findReal 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"]}'
Response200
{
  "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.

domainsarrayoptional

Or just domains.

csvstringoptional

Or CSV text with a header row; the website and name columns are detected (override with domainColumn / nameColumn).

confirm / dryRunbooleanoptional

The cost gate, as everywhere.

EndpointWhat it does
POST/qualify/estimateThe quote for the screen. Free.
POST/qualifyUp 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"]}'
Response200
{
  "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.

EndpointWhat it does
GET/playsEvery play with its input and steps.
GET/plays/{id}One play and its recent runs.
GET/plays/{id}/runsRun history (last 5).
POST/plays/{id}/estimateItemized quote for the whole play. Free.
POST/plays/{id}/runThe whole play as a job: {"input", "confirm"} → 202 with a job id to poll or stop (see Jobs).
POST/plays/{id}/sourceStep 1 only: the sourced companies.
POST/plays/{id}/angleStep 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}'
Response202
{
  "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.

EndpointWhat it does
GET/jobs/{id}status, step N of M, the current stage, stages, costUsd, summary and, for plays and screens, result.
POST/jobs/{id}/stopStop 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

StatusMeaning
queuedWaiting for a free slot; position is its place in line. It starts by itself, first in, first out.
runningWorking; stages fill in as each one lands.
doneFinished. Plays and screens carry result; research, analyses and plans saved theirs on the account.
failederror says why.
stoppedStopped 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.
stalledNo 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.

KindAt once
Fit checks3
Research runs, and one account's outreach or call4
Analyses6
Plays2
Qualify screens over 20 rows2

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"
Response200
{
  "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.

EndpointWhat it does
GET/workspaceName, included runs left, capabilities, research cost per run, the key's limits.
GET PATCH/contextThe 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/context/suggest{"what": "why" | "segments"}: suggested items, never saved.
GET/signalsBuilt-in signals, the signal library and this workspace's selection.
PUT/signals/config{"builtin"?, "library"?}: a key you leave out keeps its current value.
GET/costsDollars spent across the workspace, by stage and by account.
GET/research/estimateWhat one research run costs here.
curl https://app.aiseoworkflows.com/api/v1/workspace \
  -H "Authorization: Bearer $AI_KEY"
Response200
{
  "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.

Server URL
https://app.aiseoworkflows.com/mcp
  1. 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
  2. Step 2Connect your tool
    1. Run this once in your terminal. --scope user makes 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"
    2. Check that it connected. Run claude mcp list (account-intel shows Connected), or type /mcp inside Claude Code.
    3. 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.

  3. 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.
  4. 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" with spent: 0: the agent has to show you the price and call the same tool again with confirm: true. Nothing spends silently. Every run it starts is a job: get_job follows it, stop_job stops 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.

ToolInputsWhat it does
list_accountsnoneList every researched account with its slug, domain, fit verdict and research date.
get_accountaccount, 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_accountdomain, force?Start a research run (uses one included run). Returns the saved brief instead when one younger than 7 days exists.
ask_accountaccount, question, confirm?Answer a question from the account's saved research.
revise_draftsaccount, instruction, contact?, confirm?Rewrite the outreach drafts, optionally for one contact.
log_touchaccount, contact, channel?Record that a draft was sent (nothing is sent by the app).
list_analysesslugThe analyses that can run on an account, and the saved results of those that already ran.
get_analysisaccount, analysisOne analysis result with its status.
run_analysisslug, analysis, params?, confirm?, estimate_only?Run one analysis. Above $0.05 it only returns the estimate until called again with confirm: true.
manage_competitorsaccount, action, competitors?, instruction?get, set or suggest the competitive set. Suggestions are never saved.
find_companiesdescriptionReal companies matching a description, against the ICP.
qualify_companiescompanies, confirm?ICP fit screen for up to 300 companies; over 20 returns a job.
manage_listsaction, list?, name?, domains?list, get, create, add, remove or delete company lists.
list_playsplay?The plays, or one play with its runs and estimate.
run_playplay, input?, confirm?Run a whole play as a background job.
run_planplan, 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_planaccountAn account's fit check and its outreach and call runs.
get_jobjobStatus, progress and result of a background job (plans, research, analyses, plays, large qualify screens).
stop_jobjobStop a background job now; whatever finished before the stop is kept.
get_contextnoneThe workspace positioning and its signal selection.
get_costsaccount?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"}}}'
Response200
{
  "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
  1. In your Clay table: + Add column → Add enrichment, search HTTP API.
  2. Method POST, Endpoint https://app.aiseoworkflows.com/api/v1/plans/run.
  3. Headers: Authorization = Bearer ai_live_YOUR_KEY, Content-Type = application/json.
  4. Body: type / where the domain goes and pick your Domain column (Clay inserts it as a token).
  5. 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"]}
HTTP APIEnrichment column · Fit check
MethodPOST
Endpointhttps://app.aiseoworkflows.com/api/v1/plans/run
HeadersAuthorizationBearer ai_live_••••Content-Typeapplication/json
Body{ "plan": "fit", "domains": [ / Domain ] }
Run settingsAuto-run off · run on a few rows first
SaveSave and run 10 rows
Illustration of Clay's HTTP API column for the fit check (layout simplified; Clay's labels can vary slightly by plan).
3Read the verdictfree · a GET
  1. Add another HTTP API column: Method GET, Endpoint https://app.aiseoworkflows.com/api/v1/plans/ followed by the / Domain token (a domain works in place of the slug).
  2. Click a filled cell and add these as columns: fit.verdict (fit, nurture, not_a_fit, customer), fit.score (0 to 100), fit.reason and fit.stoppedAt.
  3. 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
  1. Add an HTTP API column: Method POST, Endpoint https://app.aiseoworkflows.com/api/v1/research, same two headers.
  2. Body {"company": "/Domain"}.
  3. In the column's run settings, Only run if the verdict column equals fit: stopped and parked companies never pay for research.
  4. Add the slug from the response as a column; step 5 uses it.
{"company": "/Domain"}
5Pull the signal, the buyer and the draftfree · a GET
  1. Add an HTTP API column: Method GET, Endpoint https://app.aiseoworkflows.com/api/v1/accounts/ followed by the / slug token.
  2. Run it about five minutes after step 4. Rows whose status is still running: filter and run again.
  3. 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.
  4. Lighter columns: /accounts/{slug}/signals, /people or /messages return one slice each.
GET https://app.aiseoworkflows.com/api/v1/accounts/{slug}
DomainFitScoreSignalContactEmail subject
acme.comfit81Hiring a Head of SEOJordan Lee, VP MarketingYour Head of SEO search
payroll.examplenurture54not researched··
cryptotax.examplenot_a_fit·stopped: 40 searches a month··
Illustration of the finished table: the fit check stopped two companies for free; only the fit was researched.
6Costs, limits and stopping
  • Each key allows 60 requests per minute; over the limit Clay gets 429 with Retry-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.

  1. Quote. Any call that can spend accepts dryRun: true: it answers 200 with {dryRun, spent: 0, estimate} and does nothing else. The estimate is itemized (lines[] of label + usd, then totalUsd).
  2. Confirm. Above $0.05 a call without confirm: true answers 402 with the same estimate, needsConfirm: true and spent: 0. Under $0.05 it simply runs.
  3. Poll. Long work answers 202 with a job id: research and analyses are polled on the account (GET /accounts/{slug}, GET /accounts/{slug}/analyses/{analysis}), plans at GET /plans/{slug}, plays and screens over 20 rows at GET /jobs/{id}.
  4. Stop. POST /jobs/{id}/stop stops 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.

Response402
{
  "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).

StatusMeaning
400Bad 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).
401Missing or invalid key. Create one under Settings → API & MCP.
402Spend needs confirmation (confirm: true), or the workspace's included research runs are used up.
404Unknown account, slice, list, play, plan, job or endpoint.
409Ambiguous 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.
429Over 60 requests per minute on this key; honor Retry-After.
500Server error; the body carries the message. Safe to retry reads.

Machine-readable spec: openapi.json.

Response401
{
  "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"
}