The Answer Engine: one question in, a grounded, source-checked read out

Synthesizes an answer from tracked coverage, verifies it against sources with a second model pass, and joins live prediction markets. PERSISTENCE: each answer is stored and published at an unlisted public permalink (/s/<id>, returned as id); recent answers may surface on /api/recent. Do not send personal or confidential information in questions. Stale permalinks (>~36h) redirect to a fresh re-ask. Spends real model compute per call, so the limits are tighter: 12 requests/minute burst per key, 25 free asks per key for the life of a free self-issued key (not per day — they do not reset; a used-up allowance answers 402 naming the metered next step, never a dead end), a per-key daily ceiling that is runaway-loop protection. With no key, ask answers 25 questions a day per network address (metered on the address only — no cookie, no fingerprint — so everyone behind one shared address shares it; resets at midnight UTC), behind an 8 requests/minute per-address burst guard. Every keyless answer carries allowance {state, remaining, limit, resetsAt, ownMeter}; state is cannot-measure (remaining null, never 0) when the meter could not be read. Once the day's keyless answers are spent the call returns 401 key_required whose FIRST field, selfIssue, is the ready-to-run POST /api/keys call (body, curl), followed by thenRetry and keylessAllowance {limit, used, remaining: 0, resetsAt}. The key is issued in one call at POST /api/keys (no human in the loop, no card) and carries its own independent meter, never shared with cloud or office neighbors. Synchronous answers measured 24–31 seconds (p50–p90) over 78 live asks in the last 14 days, as of 2026-10-19; set client timeouts to at least 70 seconds. ASYNC OPTION: Async submissions return a claim ticket in about a second; the finished answer is usually ready within 30–120 seconds. Pass {"async": true} to receive the claim ticket (202: {status:"working", ticketId, get:"/api/answer/<id>"}) and collect the finished answer at GET /api/answer/{ticketId} — the question is metered once at submit, collecting is free. Falls back to a synchronous answer when the ticket store is unavailable; handle both shapes. SHARED STATE: the same question asked again while the record's answer is recent (~60 min) returns the SAME stored answer immediately — servedFrom:"maintained-record", same permalink id, original answeredAt disclosed — rather than a second improvisation; pass {"fresh": true} to force new synthesis. METERING: An identical question repeated within 10 minutes against an unchanged record is one Read. A repeat of your own question inside that window comes back servedFrom:"recent-identical-ask" with metering.read.charged=false and the rule in metering.read.reason; anyone else's identical question is their own Read. Not CORS-enabled for cross-origin browsers.

post/api/ask

Request

  • Base URL: https://api.signalbureau.ai
  • URL: https://api.signalbureau.ai/api/ask
  • Auth: none declared

Request body

questionstring required

A question about the future. Trimmed; must be at least 5 characters.

contextobject[]

Optional entity context objects (the site UI passes matched entities). Server-side matching fills this for MCP callers; plain API callers can omit it.

asyncboolean

true → return a claim ticket immediately (202) and compute in the background; collect at GET /api/answer/{ticketId}. Default false (synchronous).

freshboolean

true → force a new synthesis instead of serving the maintained record's recent answer to the same question. Default false: a repeat of a recently-answered question returns the SAME stored state (servedFrom:"maintained-record", same permalink id) — the shared-state property, by design. An identical question repeated within 10 minutes against an unchanged record is one Read. — so passing true also opts out of that, buying new work at the published rate.

observerstring

Optional. The observer this question is asked as (the O of Time, Observer, Watch, Rule). Recorded as declared on the answer's replay block; default "public". A malformed value is named in replay.declared.rejected, never applied.

watchstring

Optional. The Watch this question is asked under. Recorded as declared on the answer's replay block; default none.

Response

The structured answer. verified is the source-check verdict: true (every load-bearing claim located in article-level tracked coverage), false (some claims could not be located, or a hard claim's only support was a headline — both are listed in unverified and confidence is downgraded), or null (verification unavailable). A claim supported only by a headline is flagged "supported only by a headline" and caps the verdict: headline-attributed is not source-verified. Served-from-record answers additionally carry servedFrom/answeredAt/servedNote. RESPONSE-SHAPE CHANGE (2026-08-19): answers carry no probability of Signal Bureau's own — the former probability, probabilityRationale, and lineage fields were removed. Prediction-market prices remain on markets[] rows, quoted and attributed to their market.

okboolean required
answerstring required

2–3 short paragraphs separated by blank lines. Carries no probability of Signal Bureau's own (removed 2026-08-19); any percentage in the prose is the market's or a source's, attributed.

confidence'low' | 'medium' | 'high'

Capped by verification, never independent of it (2026-09-07). Unless verified is exactly true AND verifiedBlockedBy is empty, this reads "low" and unverified carries the reason in words — a cannot-measure verdict may never render as the strongest trust signal we emit. The synthesis model's own self-assessment is an input to that ceiling, never the output.

whatWouldChangestring[]
topicsstring[]

Canonical topics to track.

verifiedboolean nullable

Source-check verdict from the second model pass; null when verification was unavailable. Three states, never two: true = checked and located, false = checked and not located, null = could not check. Both false and null cap confidence at low; only null sets verificationAbstained.

servedFrom'maintained-record' | 'recent-identical-ask'

Present only when this reply was served FROM the record instead of synthesized fresh. 'maintained-record': this question was answered recently (~60 min) and you are reading that SAME state under the same permalink id — the shared-state property, and a Read like any other. 'recent-identical-ask': YOUR OWN key asked this exact question inside the published window and the record has not moved, so no work was done and no Read was receipted — An identical question repeated within 10 minutes against an unchanged record is one Read. It is scoped to your key: one key is never served, and never spared a charge, from another key's answers. Pass {"fresh": true} for new work, metered as new work.

servedFromAtstring date-time

When this reply was served from the record. Its companion answeredAt is when the answer it serves was produced — the gap between them is the age, with no arithmetic.

servedAs'verified' | 'as-written' | 'evidence-bounded' | 'abstained'

What happened to the prose. 'verified': the verdict cleared and the text is untouched. 'as-written': the verdict did not clear but nothing in the text states more than the sources establish, so it is served unchanged. 'evidence-bounded': every sentence stating more than the cited sources establish was removed whole and the rest served verbatim, with a disclosure line saying a statement was withheld — and also the state served when the verification pass could not run at all (verificationReason 'verifier-unavailable') over an answer that has evidence: the mechanical strength check still bounds the prose, so the text is held to what the citations carry, but no verifier read it. 'abstained': the conclusion was withheld and a labeled statement served in its place (opens 'Withheld:'). Guarantee: when verificationReason is 'overstated' this is never 'verified' or 'as-written'; when the verifier did not run it is never 'verified' or 'as-written' either; and an abstention always carries abstained: true, confidence: 'low' (no response carries a probability field — answers state no probability of ours). Verifier language reaches a caller only inside the labeled abstention, never spliced into reporting. Independently of the four values, every served factual sentence has passed the sentence-grounding gate: it is carried by one row of the record the writer saw (its key terms inside one source sentence of that row; its numbers, names and dates in that row), or it was removed whole — see sentenceGrounding. A verifier verdict never lets a sentence through on its own.

abstainedboolean

Whether the conclusion was withheld and an evidence summary served instead of a synthesized answer. Always a boolean, never null — this is OUR editorial decision taken at synthesis, not a reading of the world, so it has no cannot-measure state to report; see verificationState below for the field that does carry one. True exactly when servedAs is "abstained".

sentencesServedinteger nullable

How many sentences of answer were served after the sentence-grounding gate. Null when the gate did not grade this prose (a house withholding, or no answer was written) — never a zero that reads as graded.

sentencesDroppedinteger nullable

How many sentences of the drafted prose the sentence-grounding gate removed because no row of the grounding record carried them. The removed text is never published; the count is disclosed here and in words on unverified. Null when the gate did not grade this prose.

sentenceGroundingobject nullable

The sentence-grounding gate's report (schema sb.sentence-grounding.v1). state is 'grounded' (at least one factual sentence carried by a row), 'absence-only' (no factual sentence carried; only verified absence sentences served), 'ungrounded' (rows existed, none carried the prose: the answer abstained), 'cannot-measure' (no rows to check against, or the gate could not run: the answer abstained), 'not-graded' (the prose was already a house withholding or was never written; reason says which) or, on a replay of a stored answer, 'not-recorded' when the row predates the gate. sentences[] lists each served sentence with factual and rows — the grounding row ids it stands on: a source URL, or ctx:<kind>:<hash> for a line of the record the writer saw (market, trend, entity, record, corpus, headline). Every factual entry carries at least one row id. droppedReasons counts removals by the closest failure: number, name, terms (a row held at least half the key terms the test needed: a paraphrase near miss), uncited (no row came near), absence-contradicted, and dangling (a sentence opening with a continuation or anaphoric opener from a closed list, such as 'adds that', 'also', 'it', 'however', 'the company', removed because the sentence before it in the same paragraph was removed). abstainReason says why an abstained answer was withheld: 'no-factual-sentence' (no factual sentence survived; a non-factual remainder is never served alone), 'framing-only' (the only factual sentences left described our own evidence, with no number, name or date) or 'no-rows'; null when served. citationsReconciled reports how the response's citations were matched to the served sentences after the gate (state 'reconciled' with served/added/removed counts; every served citation is a row a served sentence stands on), or null when not applicable. verificationScope says which text verified was judged on: 'as-served', or 'before-sentence-gate' when the gate removed sentences or re-matched the citations after the verifier read the prose (it is not re-run; verificationScopeNote says so in words, and the same sentence rides unverified); null when the gate did not grade the prose or the answer was withheld. ABSENCE sentences (a closed list of forms saying the record holds nothing on X: 'the record cannot speak to X', 'we hold no reporting on X', 'none of the sources mention X', 'the coverage we hold is about Y rather than X', ...) are kept only when code verifies the absence: no row contains any of X's key terms, names or numbers (else dropped, absence-contradicted). A kept absence is listed with kind: "absence" and the terms it was checked against, is not factual, and is counted in absencesServed. State 'absence-only' means no factual sentence was carried but verified absences were: the answer is served (not abstained) with verified false, verificationReason 'ungrounded' and confidence low. Deterministic: re-run over the stored prose against a bundle whose SHA-256 matches replay.generation.groundingBundleSha256, it reproduces every decision.

unverifiedstring[] nullable

Claims that could not be located in tracked coverage (present when verified=false); null when the verification pass did not produce a list (e.g. out-of-scope answers). Also carries the confidence-ceiling reason in plain words when a blocking condition capped the answer.

verifiedBlockedBystring[]

Every condition that capped this answer, machine-readable. Empty means nothing capped it. Non-empty always implies confidence "low". The first eight are properties of the CITATIONS and are exactly the conditions the machine-acceptance verdict is computed from, so any of them appearing means verified is not true. The last two are properties of the PAYLOAD's internal agreement rather than of the evidence — market-disagreement (the prose stated a market number this response's own marketReceipt or markets array contradicts) and market-unreceipted (a market-pricing claim carried no receipt on this answer a reader could check). Both REMOVE the offending sentence before the answer is served and disclose the removal on unverified; neither changes verified, because the claims that remain are sourced exactly as well as the verifier found them. verified: true beside one of these two is therefore coherent, and means: the sources checked out, and a sentence that disagreed with this payload's own market data was taken out. ungrounded is contributed by the sentence-grounding gate: no factual sentence of the prose was carried by a row of the grounding record, so the answer was withheld (servedAs: "abstained", verificationReason: "ungrounded") and verified is false — or null when the gate had no rows to check against (verificationState: "cannot-measure").

unverifiedReasonstring nullable

The single condition that set the confidence ceiling (the first entry of verifiedBlockedBy, or verifier-unavailable when the verdict itself is null). Null when nothing capped the answer.

verificationAbstainedboolean

The abstention, recorded (2026-09-07). True only for the cannot-measure verdict — verified: null, a check that could not run. A verdict of false is a check that ran and said no: a finding, not an abstention. Counting these rows gives the null-verification rate directly.

verificationState'verified' | 'unverified' | 'cannot-measure'

The same tri-state verdict as verified (true/false/null), spelled in words a machine can log or branch on without a lookup table: "verified", "unverified", or "cannot-measure" — the last meaning the check itself could not run, never that nothing was found. Additive; verified keeps its boolean-or-null shape unchanged.

verificationReasonstring nullable

The single token naming the pass that failed or could not run, drawn from the same vocabulary as verifiedBlockedBy (falling back to "verifier-flagged" or "verifier-unavailable" when the blocking list is empty). Null exactly when verificationState is "verified": a passing check has no reason to report. It also carries subject-untracked — emitted when nothing in the maintained record names the question's subject, so the answer was withheld rather than written from general knowledge (servedAs: "abstained", citations: [], verified: false). When it reads 'overstated', servedAs is 'evidence-bounded' or 'abstained' — the served text was re-graded before it shipped and contains no sentence above what the sources establish. When it reads 'verifier-unavailable' the verification pass did not run — the call is retried once inside its existing deadline before that state is declared — and the answer is served deterministically: verified null, verificationState 'cannot-measure', servedAs 'evidence-bounded' when the record holds evidence for the question and 'abstained' when it holds none. The same outage returns the same contract on every retry. When it reads 'writer-unavailable' the writing pass itself did not complete — the answer was never synthesized — so nothing was served in its place: servedAs is 'abstained', abstained true, verified null, verificationState 'cannot-measure', and any citations still listed are the evidence the answer would have been written from, named as such in the withholding text rather than offered as a checked basis. A writer outage is never reported as 'verifier-unavailable': no answer existed for the verifier to read. When it reads 'ungrounded' the sentence-grounding gate found no factual sentence of the drafted prose carried by any row of the grounding record: servedAs is 'abstained', the labeled withholding is served, verified is false and verificationState 'unverified' — or verified null and verificationState 'cannot-measure' when the gate had no rows to check against or could not run.

recordVersionstring nullable

The version of the maintained record this answer was written against — a 16-hex-character content fingerprint over the topic record the answer cites as its principal receipt. Null when the question matched no published record, which is an honest "nothing to bind to" and never a placeholder that would compare equal to a real version later.

recordBinding'fresh' | 'unbound' | 'stale' | 'unknown'

Whether this answer is still bound to the record state it cites. fresh = the record has not moved; unbound = no published record covers the question. A served answer whose record HAS moved (stale) or whose binding could not be computed (unknown) is never redistributed — it is re-synthesised against the record as it stands today, so these two values are visible on stored rows and audit events rather than on a served reply.

recordTopicstring nullable

The published record slug recordVersion is computed over (e.g. "hormuz"). Null when nothing bound.

trendsUsedinteger

How many matched entities carried multi-week trend history.

notRecordedstring[]

On a repeat served from a stored answer: the fields that answer never recorded. Each reads null, never as empty.

marketLinksobject[]
savedboolean

Whether the answer was persisted (permalink available).

idstring nullable

Shareable permalink id (/s/<id>); null if persistence was unavailable.

degradedboolean

true when the answer was produced on a degraded path (a dependency was unavailable); the reason rides degradedReason.

degradedReasonstring

Why the answer was produced on a degraded path.

Changes

No recorded changes to this operation across all 1 revision of this API.