- Facts is a citation-backed ledger: every party, date, amount and obligation the matter’s documents state, clustered so that ten documents saying the same thing become one fact with ten citations.
- Summary is a narrative: sections of claims, each claim carrying the passages behind it.
- Chronology is the timeline: dated events, extracted automatically and correctable by hand.
https://api.vaquill.ai/workspace/v1, and every call carries Authorization: Bearer vq_ws_....
Resolving a citation
This applies to all three capabilities, and it is the one thing worth reading before anything else. A citation publishes aquote, a documentId and a page range. It does not publish character offsets, and their absence is deliberate rather than an omission: the offsets we store internally index a string assembled for the extractor, complete with retrieval headers and overlapping chunk text. GET /v1/matters/{matterId}/documents/{documentId}/text strips both, so the two strings have different lengths and an offset published from a citation would land in the wrong place, further wrong the longer the document.
So: locate the quote. Fetch the document’s text, search for the quote string, and highlight what you find. That is how the product’s own citation verifier works, and it is stable across re-ingests in a way an offset is not.
sourceAvailable: false on a citation means the document it came from is no longer in the workspace. The quote is an immutable snapshot and stays accurate; there is simply nothing left to open.
Facts
Generating a ledger
202 with an operation. mode is standard (re-read everything) or incremental (read only what changed since the current set). Needs facts:run.
Two callers asking for the same matter at once do not start two runs: the second attaches to the first, and both operations poll the same set to the same terminal status. That is separate from Idempotency-Key, which makes one caller’s retry free.
Reading it
GET /v1/matters/{matterId}/fact-sets lists every generation, newest request first. The first row is the most recent attempt; the row with isCurrent: true is the ledger in force. Those are legitimately different rows after a failed generation, because a failure leaves the previous set untouched.
GET /v1/matters/{matterId}/fact-sets/{factSetId} is the header. Read coverage before you trust a ledger as complete:
coverage is the only place that says what that was. Generate again to pick up more.
isStale is computed per request against the matter’s documents as they are now, and changedDocCount says how many have arrived since. Neither is stored, and the hash behind them is not published.
GET /v1/matters/{matterId}/fact-sets/{factSetId}/facts is the ledger itself, paged. Facts come back in the reducer’s reading order: section-clustered, then dated facts ascending within a section. It is a reading order, not a chronology.
isConflicted: true means the matter’s documents state the fact more than one way. The rival statements are separate facts; the flag says they exist.
Exporting
csv, pdf or docx, returned as base64 content rather than a signed URL, because the file is rendered on demand and never stored. Needs exports:create. Over the inline ceiling you get a 413 rather than a partial file.
Matter summary
Generating one
202 with an operation. altitude is executive (a brief) or detailed (the long form); both read the same extracted evidence, and detailed changes only how it is written up. Needs summaries:run.
If an up-to-date summary already exists, nothing is re-run. The operation points at the existing one and reaches succeeded on your first poll. That is one code path rather than a second success shape, and it is honest: the work did succeed, earlier.
The sum_ id names the generation
GET /v1/matters/{matterId}/summaries/{summaryId} returns the summary with its narrative already joined: sections hold claims, and each claim holds the citations behind it. There is no sectionId or claimId to reassemble.
GET /v1/matters/{matterId}/summaries lists generations, newest first, across both altitudes. sections is null there rather than an empty array: null means “not included in this response” and [] means “nothing was written”.
A summary can refresh itself. When a matter’s documents change, an altitude that already has a completed summary is regenerated after a debounce. So the list can grow a row you did not ask for, and isStale can flip between two polls with no call from you.
Progress on the operation moves in a handful of coarse jumps rather than smoothly, because the pipeline writes to the job row only when its stage changes. Retry-After over-waits slightly between stages; honour it anyway.
There is no cancel. See What is not here.
Exporting
POST /v1/matters/{matterId}/summaries/{summaryId}/exports with {"format":"pdf"} or {"format":"docx"}. Base64 bytes, same as the facts export. A summary that is still generating answers 409 export-not-available: it exists, and there is nothing to render yet.
There is no CSV. A summary is a narrative with citations, and flattening it into rows would lose the thing being exported.
Chronology
It extracts itself
There is no launch operation here, and none is needed. Every document that finishes ingesting has its events extracted automatically, seconds later. The headless flow is:- Upload a document and poll its
document.ingestoperation tosucceeded. - Read
GET /v1/matters/{matterId}/chronology/extraction-statusuntilisExtractionCompleteis true. - Read the events.
status on that response is the internal label (pending, in_progress, completed, failed, skipped) rather than one of the five operation statuses. There is no operation here to be consistent with, and inventing a mapping would add a sixth spelling of “running” to the contract.
Reading the timeline
sortOrder=desc for the other direction.
primaryOnly=true collapses duplicate events to one per group, which is what the product’s own timeline does. Events in no duplicate group are always kept.
eventType, category and significance come back as plain strings. The columns behind them carry no constraint, so a matter can hold a value this API would refuse on a write. Read them; do not switch on a closed list without a fallback.
Correcting it
An extracted event can be wrong about a date, so the timeline is writable. This is the only capability of the three that is, which is why it haschronology:write rather than borrowing matters:write.
null clears description or eventDateEnd; every other field is backed by a required column and a null for one is refused by name. documentId is not patchable: which document an event came from is provenance, and re-pointing it would make an extracted event claim a source it was never read from.
POST .../chronology/events creates one by hand. Created events are recorded as not AI-extracted and carry no confidence, which keeps them distinguishable from the pipeline’s output forever.
DELETE .../chronology/events/{eventId} answers 204. It is a soft delete; a second delete is a 404.
Exporting
413 carrying limit and requested, so you can narrow the date range rather than receiving a partial file you would have no way to detect. eventCount on the response is exactly what the file contains.
Scopes
Read and run are separate so a reporting integration can watch without being able to spend.
exports:create is separate from every capability’s read scope for the same reason documents:download is separate from documents:read: reading an analysis and taking a copy of it out of the workspace are different risks.
What is not here
- No
deepfacts mode. It is unpublished because of a defect in the shared extraction path, not because of cost: a deep run replaces evidence written at a different model without accounting for it, which degrades citations belonging to summaries that already exist.standardandincrementalare safe. - No
forceon either generate. A caller that genuinely needs a rebuild changes something the corpus fingerprint can see. - No summary cancel. The product’s own cancel marks the job cancelled without stopping the work, and the worker can move it back to completed afterwards. Publishing that would give you a control over spend that does not stop the spend, and would take an operation terminal and then move it.
- No event stream. Polling the operation is the only completion path on this API.
- No fact review verdicts. They are per set and a regeneration resets them.
- No character offsets, anywhere. See Resolving a citation.

