Dry-run a delta batch and report coherence findings
const url = 'https://api.hydrate.sh/v1/branches/2489E9AD-2EE2-8E00-8EC9-32D5F69181C0/validate';const options = { method: 'POST', headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'}, body: '{"deltas":[{"node":{"data":{"aliases":["example"],"config":[{"contract_name":null,"description":"","external":false,"id":"2489E9AD-2EE2-8E00-8EC9-32D5F69181C0","name":"","type":""}],"constraints":["example"],"description":"","documentation_url":null,"external_kind":null,"inputs":[{"contract_name":null,"description":"","external":false,"id":"2489E9AD-2EE2-8E00-8EC9-32D5F69181C0","name":"","type":""}],"is_external":false,"is_test_node":false,"kind":null,"language":null,"name":"","outputs":[{"contract_name":null,"description":"","external":false,"id":"2489E9AD-2EE2-8E00-8EC9-32D5F69181C0","name":"","type":""}],"parent_id":null,"path_prefix":null,"protocol":null,"source_decisions":["2489E9AD-2EE2-8E00-8EC9-32D5F69181C0"],"status":"idle","user_kind":null,"verifications":[{"author":"user","id":"2489E9AD-2EE2-8E00-8EC9-32D5F69181C0","text":"example","type":null}]},"id":"2489E9AD-2EE2-8E00-8EC9-32D5F69181C0","kind":"behavior","parent_id":null},"type":"add_node"}]}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request POST \ --url https://api.hydrate.sh/v1/branches/2489E9AD-2EE2-8E00-8EC9-32D5F69181C0/validate \ --header 'Authorization: Bearer <token>' \ --header 'Content-Type: application/json' \ --data '{ "deltas": [ { "node": { "data": { "aliases": [ "example" ], "config": [ { "contract_name": null, "description": "", "external": false, "id": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0", "name": "", "type": "" } ], "constraints": [ "example" ], "description": "", "documentation_url": null, "external_kind": null, "inputs": [ { "contract_name": null, "description": "", "external": false, "id": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0", "name": "", "type": "" } ], "is_external": false, "is_test_node": false, "kind": null, "language": null, "name": "", "outputs": [ { "contract_name": null, "description": "", "external": false, "id": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0", "name": "", "type": "" } ], "parent_id": null, "path_prefix": null, "protocol": null, "source_decisions": [ "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0" ], "status": "idle", "user_kind": null, "verifications": [ { "author": "user", "id": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0", "text": "example", "type": null } ] }, "id": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0", "kind": "behavior", "parent_id": null }, "type": "add_node" } ] }'Dry-run a delta batch and get back the full coherence report over the graph that would result — never mutating the branch. Returns 200 with {valid, findings}: findings is the complete list of coherence problems (an input port with no incoming edge, a wire pinned to a missing port, or an edge whose endpoint types differ), and valid is true only when there are no error-severity findings. The verdict is in the body, so an incoherent graph is still a 200. A 4xx is returned only for a request that can’t be processed at all (a malformed body or an unknown delta type). An empty delta list reports the branch’s current coherence.
Authorizations
Section titled “Authorizations”Parameters
Section titled “Parameters”Path Parameters
Section titled “Path Parameters”The branch to validate against.
The branch to validate against.
Request Bodyrequired
Section titled “Request Bodyrequired”Request body for POST /v1/branches/{bid}/validate.
object
Insert a node.
object
A behavior or boundary node.
object
A node’s data payload; its shape varies by kind and whether the node is external.
On update, key-presence semantics apply: a field present with a null value is
set to null, while an omitted field is left unchanged. All fields are optional
at the type level — cross-field rules (e.g. behavior nodes reject user_kind;
external nodes require external_kind) are enforced server-side, so a partial
update is not blocked by “missing required field” errors that don’t apply to it.
object
Cascade-delete a node and its descendant subtree.
object
Update a node’s data, with key-presence partial semantics.
after may be empty; whether a field is present in after distinguishes
“set this field” from “leave it unchanged”.
object
A node’s data payload; its shape varies by kind and whether the node is external.
On update, key-presence semantics apply: a field present with a null value is
set to null, while an omitted field is left unchanged. All fields are optional
at the type level — cross-field rules (e.g. behavior nodes reject user_kind;
external nodes require external_kind) are enforced server-side, so a partial
update is not blocked by “missing required field” errors that don’t apply to it.
object
Move a node to a new parent (or to top-level).
parent_id must be present on the wire (use null for top-level); an absent key
is rejected, so a client bug surfaces as a clean parse error rather than an
ambiguous no-op.
Insert an edge.
object
An edge connecting two nodes’ ports.
Endpoints are given as source_handle / target_handle, each a port
id. Unknown keys are ignored rather than rejected.
Remove an edge.
object
Flatten a boundary node: delete it and promote its children to its parent.
object
Responses
Section titled “Responses”Successful Response
object
object
A single coherence finding over the graph that results from a delta
batch. locator is the node / port / edge id (a string) the finding is
about.
code is an OPEN set and is published as a plain string on purpose.
The codes are owned by the server and new ones are added additively as new
coherence rules ship. Publishing the set as a closed enum made every
generated client close it too — and a generated enum with no fallback arm
fails to deserialize the WHOLE response the first time the server sends a
code the client has not heard of. That turns every additive rule into a
coordinated release, and turns a client that is merely out of date into one
that cannot read a validate response at all.
So the contract is: a consumer must tolerate codes it does not recognize — render them, do not switch exhaustively on them. The known values are documented in the field description rather than enforced by the schema, because enforcing them here bought documentation at the cost of breaking clients.
severity stays closed: it is a two-valued verdict a consumer genuinely
must branch on, and adding a third value WOULD be a breaking change that
deserves a coordinated release.
object
Machine-readable finding code. Known values: unsatisfied_input (an input port with no incoming edge), dangling_wire (an edge whose endpoint does not exist), type_mismatch (a wired pair whose port types disagree), orphan_node (a node that traces to no source decision, in a graph that records provenance). This set is open and grows additively — treat an unrecognized code as an opaque string and render it; do not fail on it. Read severity to decide whether a finding is blocking: the first three are error and drive valid, while orphan_node is a warning and never changes it.
Example
{ "findings": [ { "code": "unsatisfied_input", "severity": "error" } ]}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" }}Branch/project not found or not accessible, OR a delta targets a node that doesn’t exist.
object
object
Returned when a delta targets a node that doesn’t exist (404). node_id
identifies the missing target.
object
Example
{ "detail": { "error": "not_found" }}Request body failed validation, OR a delta is malformed / unknown / not an object.
object
object
object
object
The 7-key per-delta envelope (always all keys present) the frontend
auto-recovery consumes. Shared by every 422 delta error; error
discriminates the cause.
object
The finite set of recovery hints a client may receive in
ApiError.recovery_hint:
- "regenerate_uuid" — mint a new UUID at `field` and retry
- "drop_field" — delete `field` and retry
- null — not auto-recoverable; user intervention needed
A closed set, so integrations can switch over the value; a new hint is added here deliberately, giving clients a chance to handle it.
Example
{ "detail": { "error": "malformed_delta_field", "recovery_hint": "regenerate_uuid" }}Per-bucket rate limit exceeded. The response carries Retry-After and the standard X-RateLimit-* headers (Limit / Remaining / Reset).
object
Example
{ "detail": "rate_limited"}