List decisions for a project
const url = 'https://api.hydrate.sh/v1/decisions?project_id=2489E9AD-2EE2-8E00-8EC9-32D5F69181C0&limit=50&offset=0';const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request GET \ --url 'https://api.hydrate.sh/v1/decisions?project_id=2489E9AD-2EE2-8E00-8EC9-32D5F69181C0&limit=50&offset=0' \ --header 'Authorization: Bearer <token>'One page of a project’s decisions, newest first.
Requires the decisions:read scope, which is not granted by
default — mint a key that asks for it. Only settled states are
served; captured and interviewing are mid-conversation and are
never returned.
The filters block echoes what was actually applied, so an empty
page is never ambiguous about which narrowing produced it.
Refusals beyond the standard envelopes:
422 state_not_readable—stateasked for a state this surface does not serve.
Authorizations
Section titled “Authorizations”Parameters
Section titled “Parameters”Query Parameters
Section titled “Query Parameters”Required. Decisions are project-scoped and the read is authorized per project.
Required. Decisions are project-scoped and the read is authorized per project.
Narrow to these states. Only settled outcomes are served; anything else is refused with 422 state_not_readable.
Responses
Section titled “Responses”Successful Response
One page of decisions for one project.
has_more is explicit rather than inferred from
len(decisions) == limit: a page that happens to end exactly on
the boundary is indistinguishable from a full one, and a caller that
guesses wrong either stops early or pages forever.
It is ADVISORY. The count and the page are two statements under read
committed, so a decision captured between them can make has_more
a row stale. Page until a page comes back short or empty rather than
trusting it as an invariant — and expect the ordering (newest first)
to shift a row onto a page you have already read if the ledger is
being written while you walk it.
object
A decision as the /v1 surface serves it.
A focused read view: the decision’s identity, anchor, state, and the fields a caller needs to reason about what has been decided. The shape is intentionally minimal and stable, so it won’t churn as the product grows.
object
Where a decision is anchored. id is a free-text reference
whose meaning depends on type (a node/boundary UUID, an error
payload key, or absent for a project-level decision).
type is an OPEN set, published as a plain string, for exactly the
reason Finding.code is — see that model. The anchor vocabulary is
server-owned and grows additively as new things become anchorable, so
a consumer must tolerate a type it does not recognize.
The narrowing the server ACTUALLY applied, echoed back.
Not a copy of the query string: state omitted means “every settled
state”, which is not knowable client-side. Without the echo an empty
page is indistinguishable from “this project has no decisions”, which
is a different fact and sends the caller looking in the wrong place.
object
Example
{ "decisions": [ { "anchor_ref": { "type": "node" } } ]}No credentials, malformed credentials, or revoked credentials. The envelope is leak-parity (same shape across all 401 paths) so an attacker cannot distinguish revoked vs. unknown via the body.
object
Example
{ "detail": "unauthenticated"}Credentials are valid but lack the scope required for this route, or the principal lacks membership in the target project.
object
object
Examplegenerated
{ "detail": { "code": "example", "message": "example" }}Resource not found OR not accessible to this principal. Leak parity: the response is identical in both cases so an attacker cannot enumerate resources via 404-vs-403 timing.
object
object
Example
{ "detail": { "code": "not_found" }}Validation Error
object
object
object
Examplegenerated
{ "detail": [ { "ctx": {}, "input": "example", "loc": [ "example" ], "msg": "example", "type": "example" } ]}Per-bucket rate limit exceeded. The response carries Retry-After and the standard X-RateLimit-* headers (Limit / Remaining / Reset).
object
Example
{ "detail": "rate_limited"}