Skip to content

Read one decision

GET
/v1/decisions/{decision_id}
curl --request GET \
--url https://api.hydrate.sh/v1/decisions/2489E9AD-2EE2-8E00-8EC9-32D5F69181C0 \
--header 'Authorization: Bearer <token>'

One decision by id.

Requires the decisions:read scope. A decision you may not read — because it belongs to another project, another altitude, or is still mid-conversation — returns the same 404 as one that does not exist.

The URL is flat (no project component) because a decision id is globally unique. Authorization is still per project: the caller’s altitude membership is resolved FIRST, then the row is loaded, its project resolved through the membership gate, and only then the altitude + state filters — every one of those failures producing the SAME 404 a nonexistent id gets, so this route is not an oracle for “that decision exists but you may not see it”.

403 no_altitude_membership is the one refusal that is not a 404, and it is raised before any id is looked at, so it says nothing about the id.

decision_id
required
Decision Id
string format: uuid

Successful Response

Media typeapplication/json
DecisionResponse
object
decision
required
DecisionOut

A decision as the /v1 surface serves it.

Deliberately narrower than the in-app serializer. The fields it omits are omitted for a reason, and the omission is the mitigation — adding one back is a disclosure decision, not a serializer tweak:

  • history — the append-only audit trail, which names the actor of every transition. Accountability data for humans in the app, not context for an agent.
  • assignee_user_id — a user id, same reasoning.
  • draft_ids / originating_interview_ref — pointers into interview transcripts, which are the candid text itself.
  • interview_session — the transcript. Never.
  • snoozed_until — in-app queue mechanics with no meaning to an external caller.
  • flag_reason — why a human flagged the row for approval. An in-app queue annotation about the review, not about the decision, and it names no behaviour an agent can act on.
object
anchor_ref
required
AnchorRef

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.

object
id
required
Any of:
string
type
required
Type

What the decision is anchored to. Known values: boundary, node, error_payload, project. This set is open and grows additively — treat an unrecognized type as an opaque string and render it; do not fail on it.

string
annotations
required
Any of:
string
capture_text
required
Capture Text
string
captured_at
required
Captured At
string
check
required
Any of:
string
id
required
Id
string format: uuid
owner_layer
required
Owner Layer
string
parent_decision_id
required
Any of:
string format: uuid
project_id
required
Project Id
string format: uuid
resulting_node_ids
required
Resulting Node Ids
Array<string>
source_layer
required
Source Layer
string
state
required
State
string
project_id
required
Project Id
string format: uuid
version
required
Version
string
Example
{
"decision": {
"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.

Media typeapplication/json
object
detail
required
string
Example
{
"detail": "unauthenticated"
}

Credentials are valid but lack the scope required for this route, or the principal lacks membership in the target project.

Media typeapplication/json
object
detail
object
code
string
message
string
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.

Media typeapplication/json
object
detail
object
code
string
message
string
Example
{
"detail": {
"code": "not_found"
}
}

Validation Error

Media typeapplication/json
HTTPValidationError
object
detail
Detail
Array<object>
ValidationError
object
ctx
Context
object
input
Input
loc
required
Location
Array
msg
required
Message
string
type
required
Error Type
string
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).

Media typeapplication/json
object
detail
string
Example
{
"detail": "rate_limited"
}
Retry-After
integer
X-RateLimit-Limit
integer
X-RateLimit-Remaining
integer
X-RateLimit-Reset
integer