Hyperstruck
API reference

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:

  • text is 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.
  • claims is 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

FieldTypeMeaning
questionstring, 1 to 4,000 charactersRequired. The question in plain words
detailconcise (default), cited or fullHow much each item carries. What each adds
entityUUIDOnly this entity's claims. An entity that was merged into another reads as the one it merged into
as_oftime with an offsetSearch the claims as they stood at that time
sincetime with an offsetShow what changed since then, for the entity named
statesettled, unanswered, conflicting, open, done or endedList the claims in this state (what they mean)
kindvalue, reading, obligation, event, view or relationList the claims of this kind
ownerUUIDList the obligations this entity is recorded as owing
due_beforetime with an offsetList the obligations due before then
limit1 to 200, default 50Rows on each page of a list; changes per entity when you send since
cursorstring, up to 400 charactersThe 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:

shapeYou get it whenWhat it gives you
pointYou send a plain questionThe claims nearest it
as_ofYou also send as_ofThe same, as things stood at that time. A line that has changed since ends "Now: …"
sinceYou send since and name an entity, in entity or by name in the questionWhat changed on each entity named, up to 16 entities
setYou send state, kind, owner or due_beforeEvery claim that matches, a page at a time
absentNothing is held to showA 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

FieldMeaning
textThe claims written out as lines, for your model to read
claimsThe claim items behind the text, best match first
shapeWhich kind of result this is: point, as_of, since, set or absent
as_ofThe moment the result describes
computed_atWhen the result was made
degradednull, or why the search could only match by wording (below)
shares_no_wordtrue when no claim shares a word with the question, so every claim was found by meaning
more_matchedHow many more claims matched the question's wording than the result could include
cursorThe 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:

TextWhen
"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 isReads
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

  • degraded is set. The search matched by wording only, and every cosine is null. embedding_unavailable means the question could not be compared by meaning this time; vectors_loading means the agent's claims are not ready to compare by meaning yet. If the wording matched nothing either, claims is empty and text says so. Search again for a full result.
  • more_matched is above 0. More claims matched than the result could include. Narrow the question, name an entity, or list them with GET /claims.
  • A line in text has no item. A point or as_of result holds up to 20 items, but text can name a few more claims as one-line entries. To cite one of those, look it up with GET /claims.
  • You sent since without naming an entity. You get the best-matching claims instead, and the text ends by asking you to name an entity.
  • You sent since with a filter. You get the whole set, and the text ends by saying since did 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

LimitValue
Searches per API key60 a minute, shared with the other claim reads (which ones), failed reads included
Claim reads in progress at once, per account4 per server
question4,000 characters
Items in claims20 for point and as_of; up to limit for a set, and up to limit per entity for since

Errors

StatusdetailWhen
401A messageThe 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
403curate_requiredreveal=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
409claims_not_replayedThe agent's claims are still being rebuilt
422Validation errorsA field is missing, unknown or out of range
422invalid_cursorThe cursor was not handed out by this read, under these filters
429claim_reads_busyThis account has too many reads in progress; retry after Retry-After (5 seconds)
429claim_reads_rate_limitedOver 60 reads this minute; Retry-After and retry_after_seconds give the seconds left in the minute
503claim_reads_busy or claim_store_unavailableThe server is busy, or the claims cannot be read right now; retry after Retry-After (5 seconds)