Skip to main content

Overview

Every claim has a fulfillment_condition in its contract. When you submit an event, the API evaluates the condition against the full array of event payloads in the claim’s event log. If the condition is satisfied, the claim immediately transitions to PENDING.

Node types

A condition tree is made of three node types:

Leaf node

Matches when a specific event type has been submitted to the claim:
{ "event": "signed_by_counterparty" }
event is the type field of a submitted event. It must be a non-empty string. A leaf node can optionally include value matching or numeric comparisons (see below).

Operator node

Combines child conditions with boolean logic:
{
  "op": "AND",
  "conditions": [
    { "event": "signed_by_A" },
    { "event": "signed_by_B" }
  ]
}
opSatisfied when
ANDAll children are satisfied
ORAt least one child is satisfied

NOT node

Negates a single child condition:
{
  "op": "NOT",
  "condition": { "event": "revoked" }
}
The NOT node is satisfied when its child condition is not satisfied. The child can be any node type — a leaf, an AND/OR operator, or another NOT.

Value matching (match)

A leaf node can include a match value to require that the event’s value field equals a specific string, number, or boolean:
{ "event": "inspection", "match": "pass" }
The leaf is satisfied only when an event with the matching type and a value equal to the match is present in the event log. Submitting that event:
{ "claim_id": "...", "type": "inspection", "value": "pass" }

Numeric comparisons

A leaf node can compare the event’s numeric value using one of five operators:
{ "event": "rating", "gte": 4 }
ComparatorMeaning
gtGreater than
gteGreater than or equal
ltLess than
lteLess than or equal
eqEqual to
Exactly one comparator per leaf node. match and comparators are mutually exclusive — a leaf node can use one or the other, not both.

Examples

Simple — single event

The claim resolves when "downloaded" is submitted:
{ "event": "downloaded" }

AND — all parties must sign

{
  "op": "AND",
  "conditions": [
    { "event": "signed_by_buyer" },
    { "event": "signed_by_seller" }
  ]
}

OR — any one of multiple triggers

{
  "op": "OR",
  "conditions": [
    { "event": "payment_confirmed" },
    { "event": "manual_override" }
  ]
}

NOT — signed but not revoked

{
  "op": "AND",
  "conditions": [
    { "event": "signed" },
    { "op": "NOT", "condition": { "event": "revoked" } }
  ]
}

Nested — threshold with an escape hatch

The claim resolves when both parties sign, or an admin overrides:
{
  "op": "OR",
  "conditions": [
    {
      "op": "AND",
      "conditions": [
        { "event": "signed_by_A" },
        { "event": "signed_by_B" }
      ]
    },
    { "event": "admin_override" }
  ]
}

Value matching — inspection with a specific result

The claim resolves only when an inspection event has value equal to "pass":
{ "event": "inspection", "match": "pass" }
Submitted as:
{ "claim_id": "...", "type": "inspection", "value": "pass" }

Numeric comparison — minimum score threshold

The claim resolves when a rating event has a value of 4 or higher:
{ "event": "rating", "gte": 4 }

Multi-step verification

{
  "op": "AND",
  "conditions": [
    { "event": "identity_check", "match": "verified" },
    { "event": "credit_score", "gte": 700 },
    { "event": "document_signed" }
  ]
}

Empty conditions arrays

ExpressionResult
{ "op": "AND", "conditions": [] }true (vacuous truth)
{ "op": "OR", "conditions": [] }false (no condition can be satisfied)

How evaluation works

On every POST /v1/events:
  1. All existing event log entries for the claim are fetched.
  2. An array of full event payloads is built from those entries, including the new event.
  3. The condition tree is evaluated recursively against the event payload array.
    • Leaf nodes check whether any event in the array matches the type and (if specified) the match value or numeric comparator against value.
    • NOT nodes negate the result of their child condition.
    • AND/OR nodes combine children with boolean logic.
  4. If the result is true, the claim atomically transitions to PENDING and the event is appended to the event log in the same database transaction.
  5. If false, only the event insert happens.
Because evaluation uses the full event log, you never need to replay events — the API always has the complete picture.

Validation

The condition tree is validated at schema creation time via CreateSchemaSchema. Invalid trees (wrong node structure, missing fields) return a VALIDATION_ERROR with details pointing to the exact path.
// ❌ invalid — leaf node uses wrong key
{ "type": "signed" }

// ✅ valid
{ "event": "signed" }