Claims search
Search an agent's claims with a question in plain words and get back the claims nearest it, as text your model can read and as items you can cite. Free, and nothing is written.
In short
Send a question to POST /agents/{agent_id}/claims/search and get back the agent's claims nearest it: once as text you can hand to your model, and once as items with their sources so you can cite them. It is free, and it never changes anything.
What you get back
A search result has two halves that say the same thing:
textis the claims written out as short dated lines, ready to pass to your model. It is built from the claims themselves, so it never says anything the claims do not, and it never decides which claim is right. Your model reads it and writes the reply.claimsis the same claims as structured items, best match first. Each one carries its value, its dates, its state and its source, so you can show where a detail came from or resolve the citation back to the passage in the original document.
Searching costs nothing and writes nothing, so you can search as often as you like and retry without an idempotency key. Results are not stored: to see the latest, search again.
Search with a question
Send the question, and optionally how much detail each item should carry:
curl -X POST \
-H "Authorization: Bearer <API_KEY>" \
-H "Content-Type: application/json" \
-d '{"question": "Where is the damage dispute on shipment HF-20418 up to?", "detail": "cited"}' \
"https://api.hyperstruck.com/agents/<AGENT_ID>/claims/search"Request
| Field | Type | Meaning |
|---|---|---|
question | string, 1 to 4,000 characters | Required. The question in plain words |
detail | concise (default), cited or full | How much each item carries. What each adds |
entity | UUID | Only this entity's claims. An entity that was merged into another reads as the one it merged into |
as_of | time with an offset | Search the claims as they stood at that time |
since | time with an offset | Show what changed since then, for the entity named |
state | settled, unanswered, conflicting, open, done or ended | List the claims in this state (what they mean) |
kind | value, reading, obligation, event, view or relation | List the claims of this kind |
owner | UUID | List the obligations this entity is recorded as owing |
due_before | time with an offset | List the obligations due before then |
limit | 1 to 200, default 50 | Rows on each page of a list; changes per entity when you send since |
cursor | string, up to 400 characters | The previous page's cursor, to get the next page of a list |
Any other field is refused with 422, and so is a time without a time zone offset.
Protected values, such as a bank account number, are withheld from the text and the items: the result reads "Ends 8765" in their place. To see them, add ?reveal=true to the URL. Only a caller holding claims:curate who may also change the agent may ask; anyone else gets 403 curate_required. More on withheld values.
Kinds of result
What you send decides what kind of result comes back, and shape tells you which one you got:
shape | You get it when | What it gives you |
|---|---|---|
point | You send a plain question | The claims nearest it |
as_of | You also send as_of | The same, as things stood at that time. A line that has changed since ends "Now: …" |
since | You send since and name an entity, in entity or by name in the question | What changed on each entity named, up to 16 entities |
set | You send state, kind, owner or due_before | Every claim that matches, a page at a time |
absent | Nothing is held to show | A line saying so, such as "The account holds no live claims." |
A set is the same list as GET /claims, one claim a line, and pages with cursor. A since result gives what GET /entities/{entity_id}/changes gives.
Response
| Field | Meaning |
|---|---|
text | The claims written out as lines, for your model to read |
claims | The claim items behind the text, best match first |
shape | Which kind of result this is: point, as_of, since, set or absent |
as_of | The moment the result describes |
computed_at | When the result was made |
degraded | null, or why the search could only match by wording (below) |
shares_no_word | true when no claim shares a word with the question, so every claim was found by meaning |
more_matched | How many more claims matched the question's wording than the result could include |
cursor | The next page of a set, or null |
Each item in claims also carries two scores. word_share is how much of the question's wording the claim shares, from 1 for the best match down to 0. cosine is how close the claim is to the question in meaning, or null when meaning was not compared.
Reading the text
When a point or as_of search finds claims, the text opens "What the account holds nearest this question:", then one line per entity and detail, with dates. The claims are the nearest the account holds, and may not answer the question: your model judges whether they do, and can quote the lines directly. When every claim was found by meaning alone (shares_no_word is true), a second line follows the opening one: "No claim shares a word with the question; these are the nearest by meaning."
When it finds nothing, the text opens with one of these lines instead:
| Text | When |
|---|---|
| "No claim shares a word with the question, and the search by meaning was unavailable for this answer." | Nothing matched by wording, and meaning could not be compared (degraded is set) |
| "The words reached no claim, and the search by meaning was unavailable for this answer." | Some claims share a word with the question but none could be shown, and meaning could not be compared (degraded is set) |
| "The account holds no live claims." | Nothing is held to show (shape is absent). With entity, it reads "The entity holds no live claims." |
| "The account held no live claims on 1 March 2026." | Nothing was held at the as_of you sent (shape is absent). With entity, it reads "The entity held no live claims on …" |
Under the lead value, other claims on the same line follow. A claim shown beside the lead or as its history also gives its opening words in its label field, such as "Also on record, 21 October 2026". The lead value, obligation lines and "Both stand" have no label.
| A claim that is | Reads |
|---|---|
| A current value | "- shipment HF-20418, delivery condition: Pallet 4 arrived with crushed corners (since 2 October 2026)." |
| Corrected by a person | "… (since 28 October 2026; corrected by Mei Tanaka on 28 October 2026)." |
| An open obligation | "Open since 9 October 2026, owed by Brightway Haulage, due 6 November 2026, overdue: …" |
| A met obligation | "Done on 4 November 2026: …" |
| Replaced by a newer claim | "Earlier, until 3 November 2026, replaced by Priya Nand's message 'moving it to Thursday': …" |
| Ended in some other way | "Earlier, until 3 November 2026: …", or "Earlier, until 3 November 2026, closed by …" |
| Ended on a date the source gave | "Earlier, until 3 November 2026, as stated: …" |
| Taken back by its author | "Withdrawn on 3 November 2026 by Priya Nand: …" |
| Set aside by a person before it held | "Set aside by Mei Tanaka on 28 October 2026: …" |
| Another live value, with no stated change | "Also on record, 21 October 2026: …" |
| Set aside by a reader, and not yet weighed against the lead | "Also stated, not yet settled: … (21 October 2026)." |
| Said in passing, not tied to this line | "Also said, 21 October 2026, not tied to this line: '…'" |
| Starting later | "From 3 November 2026: …" |
| A protected change waiting | "A change to … (27 October 2026, by Harlow ops key) waits for a person.", with label "Held for a person, 27 October 2026" |
| A bank account or PayID nobody has confirmed | "Not confirmed: … (Harlow Freight accounts, 21 October 2026). Confirm in the dashboard before paying.", with label "Not confirmed, 21 October 2026" (why) |
| Settled as both standing | "Both stand, as Mei Tanaka settled on 28 October 2026: …" |
When two sources give different values and neither replaces the other, the newest leads and the other follows as "Also on record", so your model sees both instead of one side.
What to check in every result
degradedis set. The search matched by wording only, and everycosineisnull.embedding_unavailablemeans the question could not be compared by meaning this time;vectors_loadingmeans the agent's claims are not ready to compare by meaning yet. If the wording matched nothing either,claimsis empty andtextsays so. Search again for a full result.more_matchedis above 0. More claims matched than the result could include. Narrow the question, name an entity, or list them withGET /claims.- A line in
texthas no item. Apointoras_ofresult holds up to 20 items, buttextcan name a few more claims as one-line entries. To cite one of those, look it up withGET /claims. - You sent
sincewithout naming an entity. You get the best-matching claims instead, and the text ends by asking you to name an entity. - You sent
sincewith a filter. You get the wholeset, and the text ends by sayingsincedid not narrow it.
limit applies to a set or since result, and cursor to a set result. Both are ignored otherwise. A since result that holds less than changed ends by saying so and pointing at the list to read the rest.
From an MCP client
The Hyperstruck MCP server has a search_claims tool that takes the same fields, plus agent_id. It is free and writes nothing. The server replaces secrets and personal details in the question with placeholders before sending it (which details), so the same question can find different claims through MCP than through the API.
Limits
| Limit | Value |
|---|---|
| Searches per API key | 60 a minute, shared with the other claim reads (which ones), failed reads included |
| Claim reads in progress at once, per account | 4 per server |
question | 4,000 characters |
Items in claims | 20 for point and as_of; up to limit for a set, and up to limit per entity for since |
Errors
| Status | detail | When |
|---|---|---|
401 | A message | The key is missing, malformed, revoked or expired |
403 | "Missing required scope" | The key lacks claims:read |
403 | "Subscription is not active" | The account's subscription is not active |
403 | "Not authorized" | The caller may not read this agent |
403 | curate_required | reveal=true from a caller who may not curate the agent's claims |
404 | "entity <id> is not held in this account" | entity or owner is not held, or is in a space you cannot read |
409 | claims_not_replayed | The agent's claims are still being rebuilt |
422 | Validation errors | A field is missing, unknown or out of range |
422 | invalid_cursor | The cursor was not handed out by this read, under these filters |
429 | claim_reads_busy | This account has too many reads in progress; retry after Retry-After (5 seconds) |
429 | claim_reads_rate_limited | Over 60 reads this minute; Retry-After and retry_after_seconds give the seconds left in the minute |
503 | claim_reads_busy or claim_store_unavailable | The server is busy, or the claims cannot be read right now; retry after Retry-After (5 seconds) |