API
Authenticate with Authorization: Bearer <key>. The key decides the account. Every build starts as a sample; nothing spends before you approve the criteria.
Audiences
Starting a build and steering it: the brief, the criteria, the two gates. Everything here is cheap until you approve.
Describe the audience the way you would to a person. The build always begins as a sample — a few hundred rows judged against the brief, priced in cents — so the approach can be proved before it runs at scale. Returns immediately; the agent works in the background and the first thing it will do is propose criteria for you to approve.
Returns — The new build, with its id and its opening state.
Errors — 401 No credential, or not one of ours. Present an API key or a Firebase ID token as `Authorization: Bearer <token>`.
{
"id": "run_a1b2c3d4e5f60718293a4b5c",
"title": "Operations directors at veterinary groups",
"status": "running",
"mode": "sample",
"approval": "pending",
"revision": 1,
"criteria": [],
"entity_counts": {
"candidates": {
"audience": 4820
},
"contacts": {
"audience": 1904,
"preview": 150
}
},
"estimate": {
"empty": false,
"contacts": 1900,
"contacts_range": [
1400,
2600
],
"companies": 620,
"companies_range": [
480,
800
],
"compute_usd": 24.0,
"data_usd": 380.0,
"contact_price_usd": 0.2,
"total_usd": 404.0,
"total_range_usd": [
300.0,
540.0
],
"minutes": 45
},
"billing": {
"tokens_total": 12000,
"compute_usd": 0.62,
"data_usd": 0.0,
"total_usd": 0.62
}
}Every build this account has started, newest first. Summaries only — fetch one by id for its criteria, funnel and estimate.
Returns — Your builds, newest first.
Errors — 401 No credential, or not one of ours. Present an API key or a Firebase ID token as `Authorization: Bearer <token>`.
[
{
"id": "run_a1b2c3d4e5f60718293a4b5c",
"title": "Operations directors at veterinary groups",
"status": "running",
"mode": "sample",
"approval": "approved"
}
]The current state of a build: what it understood, where it is in the pipeline it proposed, how many candidates it found and how many qualified, and the estimate once a sample has produced one.
Returns — The build's current state.
Errors — 401 No credential, or not one of ours. Present an API key or a Firebase ID token as `Authorization: Bearer <token>`., 403 Signed in, but not allowed — an unverified email or personal mailbox on first signup, or an audience that belongs to another account., 404 No build with that id.
{
"id": "run_a1b2c3d4e5f60718293a4b5c",
"title": "Operations directors at veterinary groups",
"status": "running",
"mode": "sample",
"approval": "approved",
"revision": 1,
"criteria": [
{
"id": 1,
"label": "Works at a veterinary group with more than one location",
"source": "derived"
},
{
"id": 2,
"label": "Holds an operations leadership role",
"source": "judged"
}
],
"entity_counts": {
"candidates": {
"audience": 4820
},
"contacts": {
"audience": 1904,
"preview": 150
}
},
"estimate": {
"empty": false,
"contacts": 1900,
"contacts_range": [
1400,
2600
],
"companies": 620,
"companies_range": [
480,
800
],
"compute_usd": 24.0,
"data_usd": 380.0,
"contact_price_usd": 0.2,
"total_usd": 404.0,
"total_range_usd": [
300.0,
540.0
],
"minutes": 45
},
"billing": {
"tokens_total": 12000,
"compute_usd": 0.62,
"data_usd": 0.0,
"total_usd": 0.62
}
}A correction, an answer to a question it asked, or a revision to the brief. Revisions reuse verdicts already paid for rather than restarting, so narrowing a rubric costs only what is newly judged.
Returns — Acknowledgement. The reply arrives on the trace, not here.
Errors — 401 No credential, or not one of ours. Present an API key or a Firebase ID token as `Authorization: Bearer <token>`., 403 Signed in, but not allowed — an unverified email or personal mailbox on first signup, or an audience that belongs to another account., 404 No build with that id., 409 The build is busy, or is not at a point where this makes sense yet. The message says which, and what to wait for.
{
"status": "started"
}Releases the build to start work. Nothing that builds a table or spends model tokens runs before this — probing is exempt, because the agent is allowed to find out what exists before asking you to commit to a plan. Optional body `has_data` records which contact methods must be non-empty (current business email, personal email, mobile phone, LinkedIn URL, personal address); unchecked keys mean no filter. If the criteria are wrong, send a message with the correction instead and it will re-propose.
Returns — Acknowledgement with the approval state and has_data now on the run.
Errors — 401 No credential, or not one of ours. Present an API key or a Firebase ID token as `Authorization: Bearer <token>`., 403 Signed in, but not allowed — an unverified email or personal mailbox on first signup, or an audience that belongs to another account., 404 No build with that id., 409 The build is busy, or is not at a point where this makes sense yet. The message says which, and what to wait for.
{
"status": "started",
"approval": "approved",
"has_data": {
"current_business_email": false,
"personal_email": false,
"mobile_phone": false,
"linkedin_url": false,
"personal_address": false
}
}Which contact methods the client requires to hold data. All false by default (no filter). Checked keys become requested non-empty filters on approval.
Returns — The has_data map now on the run.
Errors — 401 No credential, or not one of ours. Present an API key or a Firebase ID token as `Authorization: Bearer <token>`., 403 Signed in, but not allowed — an unverified email or personal mailbox on first signup, or an audience that belongs to another account., 404 No build with that id.
{
"has_data": {
"current_business_email": true,
"personal_email": false,
"mobile_phone": false,
"linkedin_url": false,
"personal_address": false
}
}Runs the proved pipeline against the whole population. Only available once a sample has produced an estimate, because committing means accepting a number.
Returns — Acknowledgement. The build switches to full mode and continues on the stream.
Errors — 401 No credential, or not one of ours. Present an API key or a Firebase ID token as `Authorization: Bearer <token>`., 403 Signed in, but not allowed — an unverified email or personal mailbox on first signup, or an audience that belongs to another account., 404 No build with that id., 409 The build is busy, or is not at a point where this makes sense yet. The message says which, and what to wait for.
{
"status": "started"
}A hard limit on what this build may spend. The agent stops and reports rather than crossing it.
Returns — The budget now in force.
Errors — 401 No credential, or not one of ours. Present an API key or a Firebase ID token as `Authorization: Bearer <token>`., 403 Signed in, but not allowed — an unverified email or personal mailbox on first signup, or an audience that belongs to another account., 404 No build with that id.
{
"budget_usd": 25.0
}Progress
Following a build while it works — what it has said, what stage it is on, and what it now expects the result to be.
The criteria as the agent understood them, the pipeline it proposed as a diagram, live counts, signals, spend so far, and the unlock offer once a full run has finished. This is the view that explains a result that came in smaller than expected.
Returns — Criteria, workflow graph, entity counts, billing, estimate, and unlock.
Errors — 401 No credential, or not one of ours. Present an API key or a Firebase ID token as `Authorization: Bearer <token>`., 403 Signed in, but not allowed — an unverified email or personal mailbox on first signup, or an audience that belongs to another account., 404 No build with that id.
{
"criteria": [
{
"id": 1,
"label": "Works at a veterinary group with more than one location",
"source": "derived"
},
{
"id": 2,
"label": "Holds an operations leadership role",
"source": "judged"
}
],
"has_data": {
"current_business_email": false,
"personal_email": false,
"mobile_phone": false,
"linkedin_url": false,
"personal_address": false
},
"approval": "approved",
"revision": 1,
"progress": 0.4,
"workflow": {
"nodes": [
{
"id": "n1",
"kind": "probe",
"label": "Find the language the data uses",
"status": "done"
},
{
"id": "n2",
"kind": "build",
"label": "Wide net",
"status": "running"
}
],
"edges": [
{
"from": "n1",
"to": "n2"
}
]
},
"signals": {
"keywords": [
"veterinary"
],
"exclusions": [],
"cardinals": {}
},
"entity_counts": {
"candidates": {
"audience": 4820
},
"contacts": {
"audience": 1904,
"preview": 150
}
},
"billing": {
"tokens_total": 12000,
"compute_usd": 0.62,
"data_usd": 0.0,
"total_usd": 0.62
},
"estimate": {
"empty": false,
"contacts": 1900,
"contacts_range": [
1400,
2600
],
"companies": 620,
"companies_range": [
480,
800
],
"compute_usd": 24.0,
"data_usd": 380.0,
"contact_price_usd": 0.2,
"total_usd": 404.0,
"total_range_usd": [
300.0,
540.0
],
"minutes": 45
},
"status": "running",
"mode": "sample",
"busy": true,
"unlock": {
"contacts": 0,
"unlocked": false,
"available": false,
"contact_price_usd": 0.2,
"price_usd": 0.0
}
}In order, as the client sees it. Each event carries how long its work took and what it cost, so a slow build can be accounted for rather than guessed at.
Returns — The events so far, oldest first.
Errors — 401 No credential, or not one of ours. Present an API key or a Firebase ID token as `Authorization: Bearer <token>`., 403 Signed in, but not allowed — an unverified email or personal mailbox on first signup, or an audience that belongs to another account., 404 No build with that id.
[
{
"index": 0,
"kind": "message",
"text": "Reading the brief.",
"elapsed_s": 0.4
},
{
"index": 1,
"kind": "tool",
"text": "Probed job titles",
"elapsed_s": 2.1,
"detail": "312 distinct titles"
}
]The same events as the trace, pushed as server-sent events. Pass `after` with the last index you hold and the stream replays from there, so a reconnect or a late-joining tab picks up the whole conversation rather than only what happens next.
Returns — An `text/event-stream` of trace events, one JSON object per `data:` line.
Errors — 401 No credential, or not one of ours. Present an API key or a Firebase ID token as `Authorization: Bearer <token>`., 403 Signed in, but not allowed — an unverified email or personal mailbox on first signup, or an audience that belongs to another account., 404 No build with that id.
data: {"index": 12, "kind": "message", "text": "Judging the net."}
Results
What a finished build produced: the tabs, their rows, their fill rates, and the export once the audience is unlocked.
A finished build is packaged into tabs — typically the audience itself, the companies behind it, and anything that did not match. Each is identified by a public id (the tab label); warehouse table names are never returned.
Returns — The tabs, with row counts.
Errors — 401 No credential, or not one of ours. Present an API key or a Firebase ID token as `Authorization: Bearer <token>`., 403 Signed in, but not allowed — an unverified email or personal mailbox on first signup, or an audience that belongs to another account., 404 No build with that id.
[
{
"id": "Contacts",
"label": "Contacts",
"rows": 1904,
"kind": "tab"
},
{
"id": "Companies",
"label": "Companies",
"rows": 622,
"kind": "tab"
}
]Paginated contact rows from the shared unlocked-contacts store. Requires the unlock to have been paid — before that this returns a payment error. Pass `tab` as the public tab id from /tables.
Returns — A page of rows, with visible columns and full column specs.
Errors — 401 No credential, or not one of ours. Present an API key or a Firebase ID token as `Authorization: Bearer <token>`., 402 The card was declined, or the account has no card on file yet., 403 Signed in, but not allowed — an unverified email or personal mailbox on first signup, or an audience that belongs to another account., 404 No build with that id.
{
"tab": "Contacts",
"columns": [
"FULL_NAME",
"JOB_TITLE",
"COMPANY_NAME",
"CURRENT_BUSINESS_EMAIL"
],
"column_specs": [
{
"name": "FULL_NAME",
"visible": true
},
{
"name": "JOB_TITLE",
"visible": true
}
],
"rows": [
{
"FULL_NAME": "Dana Whitfield",
"JOB_TITLE": "Director of Operations",
"COMPANY_NAME": "Cedar Veterinary Group",
"CURRENT_BUSINESS_EMAIL": "dana@cedarvet.example"
}
],
"total": 1904
}How much of the audience is actually reachable, by channel. Aggregates only — safe to read before unlock. Pass `tab` as the public tab id from /tables.
Returns — Counts and per-channel fill rates for one tab.
Errors — 401 No credential, or not one of ours. Present an API key or a Firebase ID token as `Authorization: Bearer <token>`., 403 Signed in, but not allowed — an unverified email or personal mailbox on first signup, or an audience that belongs to another account., 404 No build with that id.
{
"total": 1904,
"counts": [
{
"label": "rows",
"value": 1904
},
{
"label": "companies",
"value": 622
}
],
"fill_rates": [
{
"label": "Business email",
"filled": 1637,
"total": 1904,
"rate": 0.86
},
{
"label": "Mobile",
"filled": 781,
"total": 1904,
"rate": 0.41
},
{
"label": "LinkedIn",
"filled": 1771,
"total": 1904,
"rate": 0.93
}
]
}The audience as CSV from the unlocked contacts store. Requires the unlock to have been paid — before that this returns a payment error rather than a truncated file, because a half-delivered audience is worse than none. Pass `tab` as the public tab id.
Returns — The tab as CSV.
Errors — 401 No credential, or not one of ours. Present an API key or a Firebase ID token as `Authorization: Bearer <token>`., 402 The card was declined, or the account has no card on file yet., 403 Signed in, but not allowed — an unverified email or personal mailbox on first signup, or an audience that belongs to another account., 404 No build with that id.
FULL_NAME,JOB_TITLE,COMPANY_NAME,CURRENT_BUSINESS_EMAIL
Dana Whitfield,Director of Operations,Cedar Veterinary Group,dana@cedarvet.example
Your own data
Lists you supply, for suppressing contacts you already have or matching your records back against the graph.
A suppression list to exclude contacts you already have, or your own records to match back against the graph. CSV, TSV or Excel. Columns are detected on upload and reported back, so a mis-parsed file is caught before it is used.
Returns — What was read from the file.
Errors — 401 No credential, or not one of ours. Present an API key or a Firebase ID token as `Authorization: Bearer <token>`., 403 Signed in, but not allowed — an unverified email or personal mailbox on first signup, or an audience that belongs to another account., 404 No build with that id.
{
"name": "existing_customers.csv",
"rows": 4211,
"columns": [
"email",
"company",
"domain"
]
}Everything supplied to this build, with what was read from each.
Returns — The files in this build's workspace.
Errors — 401 No credential, or not one of ours. Present an API key or a Firebase ID token as `Authorization: Bearer <token>`., 403 Signed in, but not allowed — an unverified email or personal mailbox on first signup, or an audience that belongs to another account., 404 No build with that id.
[
{
"name": "existing_customers.csv",
"rows": 4211,
"bytes": 214882
}
]Billing
The prepaid balance, the card behind it, and paying for an audience. Compute meters as it is spent; contacts are charged once, at unlock.
What is on the balance, what has actually been paid, and what has been spent. The ledger behind this is append-only, so any figure here can be reconstructed line by line.
Returns — The account's financial position.
Errors — 401 No credential, or not one of ours. Present an API key or a Firebase ID token as `Authorization: Bearer <token>`.
{
"balance_usd": 63.18,
"paid_usd": 125.0,
"spent_usd": 61.82,
"card_on_file": true,
"minimum_usd": 5.0,
"topup_usd": 5.0
}Returns a client secret for the payment form in the browser. The card goes to the processor, never to us — nothing here ever holds a card number.
Returns — A client secret for the browser to complete card collection with.
Errors — 401 No credential, or not one of ours. Present an API key or a Firebase ID token as `Authorization: Bearer <token>`., 402 The card was declined, or the account has no card on file yet.
{
"client_secret": "seti_1234_secret_5678",
"setup_intent_id": "seti_1234"
}Confirms the card collected in the browser and takes the opening deposit. From then on the balance re-ups automatically before it runs out, so a build does not stop halfway.
Returns — The account's position after the opening deposit.
Errors — 401 No credential, or not one of ours. Present an API key or a Firebase ID token as `Authorization: Bearer <token>`., 402 The card was declined, or the account has no card on file yet.
{
"balance_usd": 5.0,
"paid_usd": 5.0,
"spent_usd": 0.0,
"card_on_file": true
}Credit is sold in three fixed packs below face value: 5% off at $100, 10% at $250, 15% at $500. Face value raises the same prepaid balance that metered compute and unlocks draw from — not a separate wallet. The ledger records the cash and the balance separately, so the discount is visible rather than folded in.
Returns — The account's position after the purchase.
Errors — 401 No credential, or not one of ours. Present an API key or a Firebase ID token as `Authorization: Bearer <token>`., 402 The card was declined, or the account has no card on file yet.
{
"balance_usd": 563.18,
"paid_usd": 575.0,
"spent_usd": 61.82,
"card_on_file": true
}A quote, not a charge. Contacts are priced individually and charged once; a build already unlocked says so here rather than quoting again.
Returns — The quote, and whether it has already been paid.
Errors — 401 No credential, or not one of ours. Present an API key or a Firebase ID token as `Authorization: Bearer <token>`., 403 Signed in, but not allowed — an unverified email or personal mailbox on first signup, or an audience that belongs to another account., 404 No build with that id.
{
"contacts": 1904,
"price_usd": 380.8,
"contact_price_usd": 0.2,
"unlocked": false,
"balance_usd": 63.18
}Charges for the contacts once and releases the export. Calling it twice does not charge twice — the ledger refuses a second entry against the same build. If payment clears but the copy fails, unlock again to finish delivery.
Returns — What was charged and what the balance is now.
Errors — 401 No credential, or not one of ours. Present an API key or a Firebase ID token as `Authorization: Bearer <token>`., 402 The card was declined, or the account has no card on file yet., 403 Signed in, but not allowed — an unverified email or personal mailbox on first signup, or an audience that belongs to another account., 404 No build with that id., 502 Payment cleared, but the contacts could not be copied yet. Unlock again to finish — you will not be charged a second time.
{
"contacts": 1904,
"charged_usd": 380.8,
"balance_usd": 0.0,
"unlocked": true
}Account
Who is signed in behind the credential on this request.
The account behind the presented credential — an API key or a Firebase ID token. First browser sign-in with a verified work email creates the account; a personal mailbox is refused with 403 before a row is written.
Returns — The caller's id, email, name and role.
Errors — 401 No credential, or not one of ours. Present an API key or a Firebase ID token as `Authorization: Bearer <token>`., 403 Signed in, but not allowed — an unverified email or personal mailbox on first signup, or an audience that belongs to another account.
{
"id": "u_a1b2c3d4e5f6",
"email": "ada@acme.com",
"name": "Ada Lovelace",
"role": "user",
"is_admin": false
}Balance, ledger, token usage, unlocks, and audience jobs for the signed-in account — what the Account page renders.
Returns — User profile, billing statement, ledger lines, audiences, and usage totals.
Errors — 401 No credential, or not one of ours. Present an API key or a Firebase ID token as `Authorization: Bearer <token>`.
{
"user": {
"id": "u_a1b2c3d4e5f6",
"email": "ada@acme.com",
"name": "Ada Lovelace",
"role": "user",
"is_admin": false
},
"billing": {
"balance_usd": 37.5,
"paid_usd": 0.0,
"spent_usd": 12.5,
"payment_method": false
},
"ledger": [],
"audiences": [],
"usage": {
"tokens_total": 12000,
"unlocks": 1,
"jobs": 1,
"spent_usd": 12.5,
"balance_usd": 37.5
}
}Every key issued to this account. Secrets are never stored — only a recognisable prefix, name, and whether the key is still live.
Returns — The account's API keys (no secrets).
Errors — 401 No credential, or not one of ours. Present an API key or a Firebase ID token as `Authorization: Bearer <token>`.
{
"keys": [
{
"id": "k_a1b2c3d4e5f6",
"name": "ci",
"prefix": "ab_1a2b3c",
"created_at": "2026-04-01T12:00:00+00:00",
"last_used_at": "",
"revoked_at": "",
"live": true
}
]
}Issue a key for programmatic access. The secret is returned once in this response and cannot be recovered later — only its hash is kept.
Returns — The secret (once) and the key's public record.
Errors — 401 No credential, or not one of ours. Present an API key or a Firebase ID token as `Authorization: Bearer <token>`.
{
"secret": "ab_0123456789abcdef0123456789abcdef",
"key": {
"id": "k_a1b2c3d4e5f6",
"name": "ci",
"prefix": "ab_012345",
"created_at": "2026-04-01T12:00:00+00:00",
"last_used_at": "",
"revoked_at": "",
"live": true
}
}Stop a key belonging to this account. Immediate and not reversible. A key that belongs to someone else looks the same as a missing id.
Returns — Confirmation that the key is revoked.
Errors — 401 No credential, or not one of ours. Present an API key or a Firebase ID token as `Authorization: Bearer <token>`., 404 No build with that id.
{
"revoked": true,
"id": "k_a1b2c3d4e5f6"
}Assistants
The MCP endpoint, for driving a build from your own AI assistant instead of writing these calls yourself.
JSON-RPC, for driving a build from Claude, Cursor, ChatGPT or any other MCP client rather than writing these calls yourself. Point your assistant at this URL with the same bearer key and it gets the client's half of the engagement: brief, approve, follow, read, pay. The approval gates are the same ones enforced here, so an assistant cannot start a full run against a misread brief on your behalf. Ask it to `read_documentation` first.
Returns — A JSON-RPC response. Notifications are acknowledged with 202 and no body.
Errors — 401 No credential, or not one of ours. Present an API key or a Firebase ID token as `Authorization: Bearer <token>`.
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [
{
"type": "text",
"text": "{\"id\": \"run_a1b2c3d4e5f6\u2026\"}"
}
]
}
}Reference
Documentation, in the form an agent can read.
The whole reference as one markdown document, generated from the live tool registry so it cannot fall behind. This is the form to hand an agent; humans want the guide instead.
Returns — The reference, as markdown.
# Audience Builder
Audience Builder turns a plain-English brief…