API
Intelligence endpoints — search, the life of a claim, the review queue, company-wide signals, the entity graph, and the company model.
Discovery and ops
| Path | Method | Notes |
|---|---|---|
/ | GET | Service discovery (enveloped) |
/api-docs | GET | Swagger UI when SWAGGER_ON=true |
/openapi.yaml, /openapi.json | GET | Spec generated from the Go handler types |
/healthz, /readyz | GET | Liveness and readiness |
Search
| Path | Method | Contract |
|---|---|---|
/v1/knowledge/search | GET | Meaning and keyword rankings merged, with workspace's own text search folded in. kind is all (default), insights, evidence, or outputs. On the evidence half, a relative day or a YYYY-MM-DD date is a time window, and two or more personal names in that kind of question match who was in the meeting. Optional filters: since, until, entity_id (below), and label, which keeps claims supported by a source carrying that workspace label, such as flow-tools. label does not narrow evidence hits |
Results are claims plus the evidence behind them. Everyone sees the claim. An insight hit includes
the claim body. The body is cut only past a page budget, and body_truncated says when that
happened. It also includes one supporting quotation — the strongest citation that still counts as
support and that this caller may read — with who said it, when the source occurred, and where it
sits. A citation the verifier has already rejected is not offered as that quotation. support is
verified or unverified. One quotation can cover only part of a multi-sentence claim.
citation_omitted explains a missing quotation: restricted means this person cannot read the
source, unavailable means the access check failed and the quote was left out, and none means
nothing eligible supports the claim. Fetch the insight when the body was cut, the quotation was
omitted, or you need every citation.
The quote is only shown to a caller who can read the source it came from. That check is the workspace object the citation points at — a conversation, an evidence record, a file, or a project artifact — and a failed check leaves the quote out. If workspace cannot be reached, search still answers from the claims and says that the evidence half is missing. Insight quotations are left out in that case as well.
One list holds claims, quotes, and saved files. result_type says which a hit is — insight,
evidence, or output. The kinds carry different fields, because they are different things:
| Field | On | Meaning |
|---|---|---|
result_type | both | Which half of the corpus this came from |
kind | both | A claim's own kind (fact, decision, …); for evidence, the record's kind |
snippet | both | Short extract, kept for callers that only need a scan line |
body, body_truncated | insights | The claim. body_truncated means it was cut to the page budget |
supporting_citation | insights | One quotation this caller may read: quote, author, source time, locator, and whether support is verified |
citation_omitted | insights | Why there is no quotation: restricted, unavailable, or none |
confidence, independent_source_count, last_observed_at | insights | How much the claim rests on, and when it was last seen |
status, epistemic_state | insights | Where it sits in review, and how far it is trusted |
project_ids | insights | The projects whose evidence produced it. Plural: a claim three projects reached separately cites all three |
source_kind, author, occurred_at, seq | evidence | Which feed it came from, who said it, and when |
occurred_at, ingested_at, effective_from, effective_until | insights | Three different times, see below |
project_id, conversation_id, record_id | evidence | Where it is filed and what it belongs to |
Filters and the three times
since, until, and entity_id are optional.
sinceanduntilare RFC 3339 instants, such as2026-09-01T00:00:00Z.sinceis inclusive anduntilis exclusive. They apply to occurrence time. A bad value is a 400, and so is anuntilthat is not aftersince.entity_idkeeps claims that are connected to that entity in the graph, in either direction. It also keeps evidence written by that person, matched on the person's work email. For an entity that is not a person, or a person with no email on record, no evidence is kept. An unknown id is a 404.- A hit with no known time never matches a time window. The service does not guess that an undated claim is recent.
- The filters are applied before the lists are fused, so a hit outside the window cannot take a
position from one inside it. Evidence hits that a filter removes are counted in
excluded_by_filter. Workspace is not asked to filter, so a narrow window over a busy topic can return fewer thanlimit.
A claim carries three times, and each means one thing:
| Field | Meaning |
|---|---|
occurred_at | When the thing was said, or when a claim was last seen in a source. This is what since and until read. For a claim it is the same value as last_observed_at |
ingested_at | When this service took the claim in |
effective_from, effective_until | The period a decision or claim is meant to hold for. A claim's own validity window, not when it was said |
Reindexing, re-embedding, or re-reading a source changes none of these. An old note that is indexed
again today still has its old occurred_at, and still falls outside a recent window.
result_type used to be called kind, which shadowed a claim's own kind and left a reader unable to
tell a decision from a hypothesis. Evidence search also reads all four of workspace's lists now —
turns, project artifacts, records, and utterances — where it previously read the first two, so notes
and meeting lines reach search for the first time.
Ordering is relevance first, then how far each claim is trusted. A claim several independent people have confirmed ranks above one nobody has corroborated; a stale, disputed, or still-unreviewed claim ranks below it. The adjustment is small by design — it separates two claims that both answer the question, and cannot lift a well-supported claim about something else over a precise answer.
Nothing is filtered out on trust. A disputed claim that answers the question still appears, carrying the state that says why it is doubted. Hiding it would leave the reader thinking nothing was ever said on the subject, which is worse than showing them the doubt. The context pack is the opposite case and does exclude stale and disputed claims, because an agent treats what it is given as true rather than reading a label.
Objections
| Path | Method | Contract |
|---|---|---|
/v1/knowledge/objections | POST | Claims that argue against a plan, for the desktop agent. Workspace calls it on the agent's behalf. Body: conversation_id, turn_seq, plan, audience (private or shared), optional exclude_insight_ids and limit (default 5, at most 10). Writes a record of which claims were returned |
/v1/knowledge/objections/feedback | POST | useful, already_knew or wrong for one raised claim. One rating per person, turn and claim; rating again replaces it. Idempotency-Key required |
This is the other half of the context pack. The pack gives an agent only live claims; this returns only claims that give someone a reason to think again: a disputed claim, a claim recorded as contradicting one near the plan, a decision that was replaced, an unanswered open question, or a reported problem. A claim near the plan that agrees with it is never returned, and an empty list is the usual answer.
A claim that stops being live loses its vectors, so meaning alone cannot find disputed or replaced
claims. The endpoint searches by meaning, by keyword over published and superseded claims, and then
one step out along contradicts links and supersession. Only embedding the plan calls a model.
Quotes are returned only when audience is private, and only from sources the caller can read.
In a shared conversation citation_omitted is shared_conversation, because the agent's reply is
saved where other people will read it. The design and the reasons are in
docs/intelligence/agent-desktop-objections.md.
Related claims
| Path | Method | Contract |
|---|---|---|
/v1/knowledge/related | POST | Published claims closely related to a message, for the agent-desktop panel. Workspace calls it on the person's behalf. Body: conversation_id, turn_seq, text, optional limit (default 3, at most 5). Writes a record of what was returned |
/v1/knowledge/related/actions | POST | opened or not_relevant for a claim, asked for a person. Idempotency-Key required |
The panel is for the person, not the agent (ADR 0110). Only live claims are returned, and only
claims the meaning search puts within a strict closeness cut-off; matching words reorder those
claims but never add one. Claims already returned to the caller for the same conversation are left
out, so most messages return nothing. Each claim carries the strongest quote the caller can read.
people lists up to three active people quoted behind at least two of the claims, never the caller.
When the message cannot be embedded the lists are empty and semantic_available is false. The
cut-off is scored with the relatedeval command; see
docs/intelligence/agent-desktop-related-claims.md.
Insights
A claim is opened on the evidence it quotes. handoff is work one person is giving to another.
unblock is one short question keeping that work from moving. Both are read out of a meeting,
note, or thread the same way an open_question is. A case does not create either one.
commitment is a next step from a meeting summary. Only the promote_commitments job writes one; see
Architecture.
| Path | Method | Contract |
|---|---|---|
/v1/knowledge/insights | GET, POST | Read, and assert a claim by hand. GET accepts source_type (project, project_item, project_decision, saved_output, evidence, conversation) and source_id so a client can keep only claims derived from one workspace record. It also accepts practice, epistemic_state, min_confidence, and inclusive day bounds created_after, created_before, updated_after, updated_before (YYYY-MM-DD). entity_id keeps claims connected to one person or team in the graph; with kind=decision that is the decisions the person is connected to. q matches the title or the text alongside those filters, which is how a reviewer searches candidates and rejections; /v1/knowledge/search ranks published claims only. attended=true keeps claims drawn from meetings the caller was in, and answers 502 when workspace cannot be reached. label keeps claims with at least one supporting citation from a workspace record carrying that label, such as flow-tools. Each claim in the list and the detail view includes its labels |
/v1/knowledge/insights/batch | POST | Up to 200 claims by id, without quotes or links. An id that does not exist is left out. The graph uses this so a picture of many nodes is a few requests, and the first batch can paint before the rest are read. |
/v1/knowledge/insights/{id} | GET | One claim, its quotes, and the workspace record each quote came from (source_kind, source_id, source_seq). occurred_at is when that source was said or written. A quote this caller cannot read is omitted, and quote_omitted says restricted or unavailable. A client opens the record in the review app; the locator string is the compact form of the same pointer. |
/v1/knowledge/insights/{id}/promote | POST | Publish it. Reviewers only |
/v1/knowledge/insights/{id}/reject | POST | Reviewers only |
/v1/knowledge/insights/{id}/verify | POST | Vouch for it. Changes how much we trust it, not where it is in the workflow |
/v1/knowledge/insights/{id}/supersede | POST | Replace it with a newer claim. The old claim then has status superseded and superseded_by_insight_id pointing at the new one |
/v1/knowledge/insights/{id}/challenge | POST | Contest it. Open to everyone |
/v1/knowledge/insights/{id}/resolve | POST | Close a challenge |
/v1/knowledge/evidence-with-claims | GET | Evidence record ids that have at least one claim. Not filtered by who can read them. A browser is refused; workspace calls this when someone filters the evidence or notes list to records that have claims |
/v1/knowledge/evidence/{id}/force-claim | POST | Workspace calls this after a platform admin confirms. Marks the record unread and queues an extract job with {"force": true} at interactive priority, then returns 202 with the job id. The worker skips the volume and chatter gates and tells the model it must return at least one insight. Grounding still applies, so a claim that does not quote the record is not written. A person's browser is refused |
/v1/knowledge/stats/stations | GET | How many new items each Network Observer intelligence card created since since, at most 7 days back. Insights counts claims. Graph counts entities, entity links, and relationships. Discovery counts company questions, prospecting candidates, and findings. Attention counts attention items. Counts only, company-wide, for any signed-in caller |
/v1/knowledge/evidence/{id}/insights | GET | Claims citing this record, with the quotes taken from it. Workspace is asked whether you may read the record. A record you cannot read, or one that has been deleted, answers 404 whether or not it exists or produced claims |
/v1/knowledge/facts/{id}/insights | GET | What was derived from one Company fact: A statement about the business that a platform admin has decided is settled — a legal entity name, what a product is, which market the company sells into. It is a record in workspace's fact domain, written as markdown or uploaded as a file, readable by anyone and writable only by a platform admin. Intelligence derives claims from it the ordinary way and then confirms them instead of queueing them for review, and those claims go into every project's context pack. Distinct from an insight whose kind happens to be `fact`, which is any claim the pipeline extracted from a conversation and which still goes to a reviewer., with the quotes taken from it. Unlike the evidence equivalent it asks workspace nothing, because a fact is readable by anyone signed in |
/v1/knowledge/practices | GET | The Practice: The area of expertise a claim belongs to: marketing, engineering, design, construction. Knowledge is company-scoped, so practice rather than project is what partitions the corpus. the knowledge base is divided into, each with its description |
/v1/knowledge/practices | POST | Reviewer only. Adds a practice: slug, name, and an optional description |
/v1/knowledge/practices/{slug}/update | POST | Reviewer only. Changes a practice's name and description. The slug cannot change. New claims are filed using the new description; existing claims stay where they are until the practice is re-filed |
/v1/knowledge/practices/{slug}/refile | POST | Reviewer only. Queues a background job that asks the model where each live claim in the practice belongs, using every practice's current description, and moves the ones that belong elsewhere along with their authority signals. One model call per claim, 500 claims per job. Returns 202 with the job id |
/v1/knowledge/labels | GET | Every source label that reaches at least one claim, with how many claims carry it. Labels are set on workspace evidence and copied here when the record is observed or relabelled |
Anyone can challenge a claim, on purpose. Letting people weaken a claim is cheap and stopping them is expensive. Asserting a new claim is the other way round.
A claim written by hand still needs a quote to point at. POST /v1/knowledge/insights without one is
refused with its own error code rather than a generic validation error, because "you have to point at
evidence" is different advice from "this field is the wrong shape".
The review queue
| Path | Method | Contract |
|---|---|---|
/v1/knowledge/candidates | GET | Open Candidate: A proposed insight waiting on a decision. Most are resolved without a person: well-grounded corroborated claims promote automatically, poorly grounded ones auto-reject, and anything unreviewed past its deadline expires rather than accumulating., oldest deadline first |
/v1/knowledge/candidates/{id}/accept | POST | Promote to an insight |
/v1/knowledge/candidates/{id}/reject | POST | Close it |
Most candidates never reach a person. Claims with solid quotes and more than one source publish automatically. Claims whose quotes did not hold up are rejected automatically. Anything nobody reviews before its deadline expires.
Company-wide signals
| Path | Method | Answers |
|---|---|---|
/v1/knowledge/pulse | GET | What is happening, by practice and window |
/v1/knowledge/links | GET | Related work, and duplicated effort |
/v1/knowledge/who-knows | GET | Where knowledge is concentrated — by practice, topic, or the whole corpus |
/v1/knowledge/bottlenecks | GET | Where open questions pile up unanswered. A count, an age, and a resolution rate. Not the questions |
/v1/knowledge/bottlenecks/questions | GET | The published open questions that mention person, oldest first, plus an outline that names each one. person is a display name, slug, alias, or entity id |
/v1/knowledge/follow-through | GET | What projects started and did not finish |
Duplicated effort means similar work being done by people who share no project. Similar work inside one team is just that team working, not a finding. Reporting it would teach people to ignore the alert.
Everything except the pulse reads from a summary built overnight. Working these out per request means scanning most of the knowledge base, which is a query of several seconds sitting inside a page load.
Areas
An area is a group of related decisions and facts. Most are found by a nightly clustering job; a reviewer can also create one by hand. How both kinds are built is covered under architecture.
| Path | Method | Contract |
|---|---|---|
/v1/knowledge/areas | GET | Every area, largest first. origin is induced or manual; status is pending until a new manual area has been filled |
/v1/knowledge/areas | POST | Create a manual area from a name and an optional description. Answers 202 with the area in pending, and queues the populate_area job. 409 when an area with the same name exists or the name is blocked. Reviewer only |
/v1/knowledge/areas/{areaId} | DELETE | Remove an area. An induced area is also blocked, so the nightly job does not recreate it; the response says whether it was. Reviewer only |
/v1/knowledge/areas/{areaId}/members | GET | The decisions and facts in one area, nearest its centre first |
/v1/knowledge/areas/{areaId}/deciders | GET | Who appears to make the decisions in one area |
/v1/knowledge/areas/blocked | GET | Areas the nightly job will not recreate |
/v1/knowledge/areas/blocked/{blockId} | DELETE | Lift a block. The area returns on the next nightly run if the clustering still finds it. Reviewer only |
/v1/knowledge/areas/memberships | POST | Which areas each of up to 500 insight_ids sits in. Insights in no area are left out. A read sent as POST because the list is long |
The graph
| Path | Method | Contract |
|---|---|---|
/v1/entities | GET | People, teams, systems, and the rest. q matches a display name, slug, or alias |
/v1/entities/{id}, /{id}/edges | GET | One node and its relationships |
/v1/entities/{id}/offices | GET | Officer appointments read from governing documents. For a person, the offices they hold; for a company, who holds office there. Each row has the office id, the person, the company (empty when the document named a group of companies), the office, the quote, and the source claim |
/v1/offices | GET | Every office held by an active person, ordered by company, with the offices that name no single company last. Shown on the Authorities page, in the Officers tab |
/v1/offices/{id}/remove | POST | Reviewer only. The person no longer holds the office. Returns 204 |
/v1/offices/{id}/dismiss | POST | Reviewer only. The extractor got the office wrong. Returns 204 |
A removed or dismissed office stops showing, and reading the same document again does not bring it
back. Once the person holds no office at the company, their officer_of edge to it is deleted.
| /v1/graph/subgraph | GET | What is around a starting point, up to a given number of hops |
| /v1/graph/overview | GET | The most connected nodes in the whole graph, and the edges among them. limit is how many nodes, from 1 to 10,000, and defaults to 200. No starting point |
| /v1/graph/area/{areaId} | GET | One area's part of the graph: its member insights (marked inArea), and with connected (the default) the nodes one edge away from them. Only edges that touch a member are returned. limit caps the members, from 1 to 2,000, default 500, nearest the centre first. 404 when the area does not exist |
People
| Path | Method | Contract |
|---|---|---|
/v1/people/resolve?email= | GET | One person by work email: formal title and where it came from, manager, direct reports, and the accounts they are known by |
/v1/people | GET | The roster in the same shape without reports |
/v1/people/{id}/depart | POST | Mark a person as having left. Removes their authority and practice tags; they stay in the graph. Reviewer only |
/v1/people/{id}/reinstate | POST | Set a departed person back to active and queue an authority rebuild. 409 if they are not departed. Reviewer only |
/v1/people/{id}/practices | GET | The practices a person works in, inferred or set by a reviewer. A removed tag is not listed |
/v1/people/{id}/practices/{slug}/confirm | POST | Confirm an inferred tag, or add one. Reviewer only |
/v1/people/{id}/practices/{slug}/remove | POST | Remove a tag and keep it removed. Reviewer only |
/v1/people/me/claims | GET | The live published claims connected to the caller's own person entity, newest first, with where each citation points: source kind and id, line, character offsets, author. No quote text. For workspace's GET /v1/me/known (ADR 0111), which keeps only the citations from records the caller was present for and reads the quotes itself. An unknown or ambiguous caller gets an empty list. limit defaults to 200, at most 500 |
/v1/knowledge/practices/{slug}/people | GET | Everyone tagged with a practice, with their activity in it, their share of the practice's activity (practice_activity_share), and their influence. sort=influence (the default) or sort=activity |
Any signed-in caller can read the two lookups, and so can a service acting for a person (the service
token plus X-Flow-On-Behalf-Of). People are company-wide, like claims. This service does not use
Cedar. Its routes check that the caller is authenticated, and a reviewer role for the write routes.
The response is snake_case inside the usual data envelope:
{
"person_id": "uuid",
"name": "Sara Garner",
"email": "[email protected]",
"title": "Senior Engineer",
"title_source": "directory",
"effective_from": "2026-09-04T00:00:00Z",
"manager": { "person_id": "uuid", "name": "AJ Nead", "email": "[email protected]", "title": "Product Manager" },
"reports": [],
"links": [{ "system": "login", "external_id": "[email protected]", "status": "accepted" }],
"ambiguous": false
}- An email that no one has is a 404.
title_sourceisdirectory(an org import),user(the person corrected it), orinferred.effective_fromis when the current title took effect. Earlier titles and managers are kept with their dates in the database but are not returned here.linkslists the accounts a person is known by:slack,github,azure_devops,zoom,hr, andlogin(the work email).acceptedmeans the link was confirmed, either exactly or by a reviewer. Only accepted links attribute activity to the person.reviewmeans a possible match is waiting for a reviewer. It is shown so a caller can see it exists, and nothing is attributed through it.ambiguousis true when the lookup cannot tell which person is meant: two people share the name, or a different person has a pending link for the same email. In that case the response has notitle, nomanager, and noreports. Treat it as "not known", not as "has no title".- The roster is every person with a work email, a current title, or a place in the reporting structure. A name that only appeared in a transcript is not on it.
Titles and managers come from the org import. See Org import.
Each person on the roster and in the lookup also carries practices, the same tags the practices
route returns. How tags are inferred is under
Authority and practices.
Authority
| Path | Method | Contract |
|---|---|---|
/v1/knowledge/authorities | GET | Observed authority claims by practice, with influence, the person's activity in the practice, confidence, evidence_mass, effective_sources, and a breakdown by signal kind |
/v1/knowledge/authorities/{personId}/decisions | GET | Every signal behind one person's claims: the record it came from, the kind, the sender, the observation mode, and the quote |
| An observed claim is advisory and grants nothing. Officer appointments do not feed the ranking; they | ||
are listed on the person and the company, under /v1/entities/{id}/offices. |
Company model
| Path | Method | Contract |
|---|---|---|
/v1/portfolio/model | GET | The compiled three-layer Company model: The structured three-layer model of Flow that the knowledge layer compiles into: identity (human-owned), strategy (proposed then confirmed), and operating facts (automatic, each with an as-of date). Nothing is deleted; a change is superseded. Distinct from a company fact, which is the admin-written source text a claim can be derived from.: identity, strategy, and dated operating facts. bet1_managed_units counts only Unit role: Whether a property is owned, a joint venture, managed for a third-party owner, sold, or unresolved. Only managed units count toward the third-party management bet. Owned and JV growth must not be scored as that scale. units. Authenticated callers may read it |
/v1/portfolio/terrain | GET | Named metric: A closed set of operating numbers the insight harness may ask for by slug: occupancy, leased percent, units, Bet 1 managed units, vacant units, vacant rent, asking rent, marketing spend, and the audience and hospitality counts. The agent never writes SQL. Adding a metric is a migration., peer baselines, entity-time rollups, attention share, and the decision log. Empty until compile_portfolio_terrain has run. Authenticated callers may read it |
/v1/portfolio/tools/overview, /compare, /baseline, /timeline, /attention, /decisions, /metrics, /search, /drilldown | GET | The harness tools. Named metrics only; no free-form SQL. Drilldown returns permission-filtered workspace snippets and provenance refs, not a finding. Authenticated callers may read them |
/v1/portfolio/findings | GET | The executive memo (when compile_portfolio_memo has run), shadow Portfolio brief: A company-altitude story clustered from building-level findings: a thesis, a material number, a recommendation, and where the story could be wrong. Shadow only; not published as an insight., the building-level Portfolio finding: A harness-produced claim about the company model. It must name a metric, join at least two sources, state a deviation from a baseline, and name a decision it would change. Shadow findings are for review and are not published as insights. The harness clusters them into portfolio briefs. they rest on, and reviewer precision (precision, brief_eligible, consecutive weeks). Authenticated callers may read them |
/v1/portfolio/findings/{id}/accept, /reject | POST | Reviewer only. Marks a shadow finding reviewed. Optional why.detail or data.feedback is stored on insight memory. Does not publish an insight |
See Company model for the layers and write rules.
For workspace
| Path | Method | Contract |
|---|---|---|
/v1/projects/{id}/knowledge-sections | GET | The knowledge half of the context pack |
This is served from a cache. Ingest marks the cache out of date as the project moves on, and a job rebuilds it. Working it out on every request would put a vector search in front of every single agent reply.
Each insight carries a source: project when the project's own evidence produced it, related when
it came from elsewhere in the corpus and merely resembles the project, fact when it came from
something a platform admin settled about the business. Anything rendering these has to keep them
apart, or a reader is told their project decided something another project decided.
The companyFacts section is compiled once for the whole company rather than per project, and served
whether or not this project's own pack is rebuilding. A stale company section is not served, the same
rule the project pack follows: its contents are known to be wrong, and a withdrawn fact in a prompt is
worse than no facts in that prompt, because the citation makes it look checked.
Admin
Reviewer only. The scheduled enqueues Kestra posts to /internal/jobs/*, reached with a person's
credential rather than the scheduler secret, plus the per-project and corpus-wide repairs that have no
schedule. The batch worker still does the work, except where noted. The harness tool
list_queue_errors reads the dead-letter list and the failing rows of the health table. See
Operations.
| Path | Method | Contract |
|---|---|---|
/v1/admin/jobs/depth | GET | Pending and claimed counts per kind. What Kestra records on the maintenance flow |
/v1/admin/jobs/health | GET | One row per kind: counts, oldest wait, longest in-flight run, last-day duration, latest dead-job error |
/v1/admin/jobs/dead | GET | Dead-letter queue, newest first. Filter by kind, target_kind, target_id, or project_id |
/v1/admin/jobs/dead/requeue | POST | Requeue every matching dead job. Same filters as the list, no page. Jobs whose equivalent is already pending are skipped |
/v1/admin/jobs/{id}/requeue | POST | Run one dead job again |
/v1/admin/jobs/{id}/expire | POST | Retire one dead job without running it |
/v1/admin/jobs | POST | Enqueue one job (kind, plus project_id for the per-project kinds, mode for clear_all_derived). question_generate reads the practice list. An optional area limits that one run to a single phrase. |
/v1/admin/flows | POST | Enqueue every step of a committed Kestra flow, in that file's order |
/v1/admin/projects/{id}/derived-insights/clear | POST | Remove or dispute what extraction wrote from one project's evidence. Does the work inline rather than queueing it |
/v1/admin/reevaluate | POST | Force a new extract of a project or one workspace record (project, project_item, project_decision, saved_output, evidence, conversation). Marks prior reads unread. A job that already said done cannot skip this. Scratchpads, opted-out projects, and legal hold stay skipped. |
Every admin path needs a reviewer, with one exception. The job reads (depth, health, dead)
and the dead-job actions (both requeues and expire) also answer a service-token call made on
behalf of an address in INTELLIGENCE_QUEUE_OPERATORS. That setting covers every Foundry agent
mailbox, so an agent can read a dead job, and put it back once the cause is fixed, with
list_queue_errors, requeue_queue_jobs, and expire_queue_job. Running a job and the other
admin paths still need a reviewer.
Beyond the scheduled kinds, /v1/admin/jobs takes rebuild_packs, reclaim, compile_pack,
compile_company_facts, backfill_artifacts, link_projects, relink_authorship,
promote_commitments, and clear_all_derived. relink_authorship with an evidence_id relinks one
workspace record. Without one, it queues a relink for every record workspace says a model wrote, so
claims extracted from those records stop counting as a person's. promote_commitments with an
evidence_id promotes one meeting's next steps. Without one, it queues that for every meeting with a
ready summary, which catches up summaries written before intelligence read them. Running it again
does not duplicate claims. reclaim runs
inline, because a queue whose workers are stuck cannot repair itself through the queue.
backfill_artifacts and compile_pack need a project_id; compile_company_facts takes none,
because there is one company section for every project. See
Operations for when each is the right one.
Enveloped All /v1/* JSON uses the Response envelope: The template service wraps every JSON response in {header, payload}. A success payload is {data} (a record or a list) plus optional meta.pagination; accounting still returns bare bodies and RFC 9457 errors until the divergence is resolved., and
every mutation is a Command: An HTTP POST that changes state, recorded with an Idempotency-Key so retries do not double-apply; not CQRS or a command bus.: POST, an Idempotency-Key: A caller-supplied header, stable across retries, that lets command.Execute claim the operation once and replay the same response on duplicate requests., and a body of
{data, why}.
Error codes worth handling
| Code | Means |
|---|---|
ungrounded | Every quote the claim rests on failed verification |
not_reviewer | The action needs a reviewer and the caller is not one |
budget_exhausted | The day's spend cap for this kind of job is used up. Retry tomorrow, not now |
evidence_unavailable | Workspace could not be reached, so the quote cannot be shown |
Not in the spec
/internal/jobs/* belongs to the scheduler. It is protected by a shared secret and left out of the
OpenAPI document (ADR 0026).
Those endpoints queue the work and return straight away. See
Operations.
Investigation runs
GET /v1/discovery/investigation-runs lists the reading principal's scheduled discovery runs,
newest first. It accepts pagination[page] and pagination[pageSize] (default 5, maximum 20).
The response uses the standard envelope with payload.data and payload.meta.pagination. Source permissions are checked
again; a run's diagnostic text is withheld if a consulted record is no longer readable.
Each run identifies its model, algorithm version, trigger window and state (running, complete,
partial, or failed). Its report includes source counts, selection limits, model calls and token
usage, search steps, and each considered pair's decision. An empty packet list alone does not
establish that a successful investigation found nothing. The discovery page's Runs tab exposes
this report; older sweeps did not record it.
Packet detail also returns challenge_search. When no opposing passage was found, this explains
what was checked and what remains unknown. It is a search account, not an opposing quotation.
Each passage includes utterance_seq when the source line has one. occurred_at is when
that line was spoken. The packet's created_at and updated_at are properties of the
investigation record.
Company questions
GET /v1/discovery/questions lists questions the daily job proposed. status filters the queue:
proposed (the shortlist), needs_data, held, investigating, investigated, deferred,
already_answered, or not_useful. A question is omitted entirely when the caller cannot read
every record it cites. The title is not returned on its own.
GET /v1/discovery/questions/{id} returns the current version: why it was raised, why it matters,
the passages, what would disprove it, the next step, the six reasons it was kept, and earlier
review actions. Those reasons are sentences. There is no score. An already answered question
includes answer, at most two sentences. Text that contains markup or table characters is omitted.
A Slack reply is that text. A pointer at a note is the note's prose, when the caller can read the
note. The list includes the same reply when it was written on the review.
assignee=me on the list returns questions assigned to the caller, in any status. assignee also takes
a work email for any one person, and assignee_person_id takes a person's entity id instead (use one or
the other). state=open lists every question still waiting for an answer. state=answered lists
questions marked already answered, and each carries answered_at, the time it was last marked
answered. A question closed as not useful is neither open nor answered. state combines with status
and the assignee filters. The job sets
assignee and assignee_name from the cited lines: the person who spoke the most of them and
already has an email on the line. The status stays where it was. The Mine queue on the Questions
tab is that list. Once a day the assignee also receives each open question as a Slack direct
message. The message names the channel, meeting, or thread the question was drawn from. A reply in that thread marks the question already answered and saves the reply as a note. A message in the direct message does the same for the latest question there that is still open.
GET /v1/discovery/slack-replies counts that traffic for any signed-in caller. since and until are a half-open window and default to the last thirty days. One row per person: sent is questions posted in the window, replies are Slack answers that arrived in the window, and waiting is questions posted in the window that are still open. Counts only. GET /v1/discovery/slack-questions lists the questions for one person over the same window, including a question posted earlier when a Slack reply arrived in the window. A question the caller cannot fully read is omitted, including its title. The analytics page shows the counts on the Slack tab, and selecting a person lists those questions.
POST /v1/discovery/questions/{id}/review takes a command envelope. assign requires assignee
(an email) and assignee_name. investigate freezes that
version and queues question_investigate, which passes the question into the existing discovery
sweep with origin generated. refine writes a new version and leaves a frozen version alone.
already_answered requires answer_ref. not_useful requires a reason: duplicate,
wrong_premise, low_value, or needs_data. revisit requires revisit_at.
The Questions tab on the discovery page is this queue. A generated investigation says so on the card. It is not a finding the sources volunteered, and it is not evidence for the question.
Attention briefs
GET /v1/attention/profiles lists active profiles the caller may see. A reviewer sees every profile. Anyone else sees a profile only when the recipient is their own email. The role label is not an access grant.
POST /v1/attention/profiles creates one. POST /v1/attention/profiles/{id}/versions confirms a new version. A version needs one to five priorities. Briefs already approved keep the profile version they were built from.
GET /v1/attention/briefs lists briefs, newest period first. GET /v1/attention/briefs/{id} returns the selected items, the short unchanged watchlist, and the candidates that were left out. An item is omitted, wording included, when the caller cannot read one of its sources. withheld counts those omissions. reading says which feeds were observed. A feed that was not read is named. "No significant changes" does not mean the whole company was covered.
POST /v1/attention/briefs/{id}/review with approve freezes the wording and prepares one email. It does not send. reject retires that draft so another can be written for the same day.
POST /v1/attention/briefs/{id}/deliver sends the prepared email. If a send was attempted and no receipt was stored, the route refuses another send. POST /v1/attention/briefs/{id}/delivery/reconcile records whether that attempt arrived. not_sent is what allows one later send.
POST /v1/attention/items/{id}/feedback records an explicit action: tell_me_more, already_handled, keep_watching, not_relevant, matters_more, or assign_follow_up. already_handled requires a resolution link or a note, and it keeps the issue. assign_follow_up creates a task on a workspace project the caller can edit. Opening an item does not resolve it.
The Attention tab on the discovery page is this queue.
GET /v1/discovery/packets accepts inquiry, entity, created_after, and updated_after
in addition to status, lens, page, and limit. created_after and updated_after are
exclusive RFC3339 instants. entity is a case-insensitive match against the title, consequence,
scope, inquiry, speaker, or channel. Access is still checked before a card is counted or returned.
Job kinds
Every queue kind on the Admin page, how work moves from one to the next, and which worker runs it.
Company model
The three-layer model of Flow that intelligence compiles into — identity, strategy, dated operating facts, and the terrain of named metrics and rollups — seeded from the 2026-09-11 business model.