Give it a company website → researchers fetch & verify public evidence →
a publish-safe brief of grounded facts (currently-valid, source-cited, confidence-scored).
Base URL https://api.customer1.bucktee.org
Every endpoint except /health needs a bearer token:
Authorization: Bearer <WM_API_KEY>
Research is async — POST a company → get a job_id → poll until done → read the brief.
The group (graph key) is derived from the website and returned in the response — read the
company back with it. The first scrape of a company requires a company name; re-POSTing the
same website later just refreshes it (dedup skips unchanged evidence). List everything with /companies.
curl https://api.customer1.bucktee.org/health
# {"ok": true}
group) is derived from the website — the same site always maps to the same graph.| website | required — domain or URL, e.g. plausible.io. Determines the graph. |
| company | required for a new company (first scrape); optional when refreshing an existing one. |
| depth | optional — shallow | standard (default) | deep. Deeper = more research rounds, more queries, and the dedicated deep-research model. The job loops (gather → read the graph → research the gaps) until it stops finding new entities. |
curl -H "Authorization: Bearer $KEY" -X POST https://api.customer1.bucktee.org/research \
-d '{"website":"plausible.io","company":"Plausible"}'
# 202 {"job_id":"a1b2c3","status":"queued","group":"plausible-io","new_company":true,"poll":"/research/a1b2c3"}
#
# a NEW company with no name is rejected:
# 400 {"error":"company name is required for a new company (first scrape)","group":"plausible-io"}
status goes queued → running → done (or error). While running,
agents is the list of researchers spawned and their live status (planner / site collector /
web researcher / extractor / grounding, each running|done with a detail), activity is a
one-line "what it's doing right now", and events is the raw step log. Poll any of them for live
progress. When done, the brief is in result.curl -H "Authorization: Bearer $KEY" https://api.customer1.bucktee.org/research/a1b2c3
# running: {"status":"running","activity":"Web researcher · round 1 — searching the web",
# "agents":[{"id":"plan-0","label":"Planner · round 0","status":"done","detail":"12 queries"},
# {"id":"web-0","label":"Web researcher · round 0","status":"done","detail":"14 pages"},
# {"id":"extract-0","label":"Extractor · round 0","status":"running","detail":"building the graph"}]}
# done: {"status":"done","group":"plausible-io","stats":{...},"result":{...brief...}}
curl -H "Authorization: Bearer $KEY" https://api.customer1.bucktee.org/company/plausible-io
?build=1 to force a rebuild (takes ~a minute). fact_index maps every cite
[n] to its fact, source URL, quote, and uuid (the feedback handle).curl -H "Authorization: Bearer $KEY" https://api.customer1.bucktee.org/company/plausible-io/dossier
search_rsa, meta_ad, x_post, linkedin_post, landing_hero,
email_subjects. Every variant carries an audit: each factual claim mapped to a fact uuid
(source URL + quote) or flagged unsupported, plus platform length checks. Drafts only — nothing publishes.curl -H "Authorization: Bearer $KEY" -X POST https://api.customer1.bucktee.org/company/plausible-io/ads \
-d '{"formats":["x_post","landing_hero"],"variants":3}'
# {"formats":{"x_post":{"variants":[{"text":"...","audit":{"claims":[...],"unsupported":0,...}}]}}}
asset_ids + UTM params).utm_content=asset_id), winners & losers, and the proven angles feeding the next generation.{"rows":[{"date","channel","campaign","asset_id","spend",
"impressions","clicks","conversions","revenue","source"}]}) — the ingest seam for kimchi or custom scripts.{"since":"YYYY-MM-DD","write_back":true} — write_back also lands campaign/channel
performance facts in the graph (structured, no LLM).uuid from the brief or the dossier's
fact_index). Authoritative: it outranks collected evidence and persists across re-research.| fact_uuid | required — the fact's uuid |
| action | required — confirm (pin, confidence 1.0) | reject (invalidate, kept as history) | correct (invalidate + write the replacement) |
| correction | required for correct — the corrected statement |
| author | optional — who corrected it |
curl -H "Authorization: Bearer $KEY" -X POST https://api.customer1.bucktee.org/company/plausible-io/feedback \
-d '{"fact_uuid":"...","action":"correct","correction":"Plausible has 12 employees."}'
# {"action":"correct","fact":"...old claim...","replacement_uuid":"...","dossier_invalidated":true}
curl -H "Authorization: Bearer $KEY" "https://api.customer1.bucktee.org/search?group=plausible-io&q=who+are+the+customers"
curl -X DELETE -H "Authorization: Bearer $KEY" https://api.customer1.bucktee.org/company/plausible-io
# {"deleted":"plausible-io","nodes_deleted":214,"tenant_removed":true}
curl -H "Authorization: Bearer $KEY" https://api.customer1.bucktee.org/companies
# {"companies":[{"group_id":"plausible-io","company":"Plausible","website":"plausible.io",
# "created_at":"2026-06-26T15:00:00+00:00","last_research":"2026-06-26T15:02:00+00:00"}]}
agents and status —
so a reloaded UI (or any monitor) can re-discover running work without holding the job id.curl -H "Authorization: Bearer $KEY" https://api.customer1.bucktee.org/jobs
# {"jobs":[{"job_id":"a1b2c3","company":"Plausible","status":"running",
# "activity":"Web researcher · round 1 — searching the web","agents":[...]}]}
{
"group_id": "plausible-io",
"as_of": "2026-06-26",
"entities": [{"name":"Plausible","type":"Organization","summary":"..."}],
"facts": [{
"fact": "Uku is a co-founder of Plausible.",
"relation": "CO_FOUNDER", "subject": "Uku", "object": "Plausible",
"valid_from": "2000-01-01",
"grounded": true, "confidence": 1.0, "confidence_band": "high",
"source_url": "https://plausible.io/about",
"quote": "Uku is a co-founder of Plausible."
}],
"dimensions": { "people": [/* facts */], "products": [/* facts */] },
"stats": {"entities": 17, "facts_published": 8,
"excluded": {"stale": 0, "contested": 2, "unsupported": 0}}
}
Each fact carries its source URL, a verbatim supporting quote, a confidence band,
and a validity window — so you can drop facts (or a dimension) straight into a
generation prompt and trust it's current and cited. excluded = what the publish filter held back.
const BASE = "https://api.customer1.bucktee.org"; const KEY = ""; const h = { Authorization: `Bearer ${KEY}`, "Content-Type": "application/json" }; // 1) start research const { job_id } = await fetch(`${BASE}/research`, { method: "POST", headers: h, body: JSON.stringify({ website: "plausible.io", company: "Plausible" }) // company required on first scrape }).then(r => r.json()); // -> { job_id, group: "plausible-io", ... } // 2) poll until done (job.activity is a live one-liner you can show meanwhile) let job; do { await new Promise(r => setTimeout(r, 5000)); job = await fetch(`${BASE}/research/${job_id}`, { headers: h }).then(r => r.json()); console.log(job.status, job.activity); // e.g. "running" "round 2 · extracted — 14 entities, 31 facts" } while (job.status === "queued" || job.status === "running"); // 3) use the brief const brief = job.result; // or GET ${BASE}/company/plausible-io later console.log(brief.facts);