Billable conditions

The billable condition is a flat list of leaves that determines the current provisional outcome before settlement.

Overview

Every outcome has a condition in its contract. When you submit an event, the HTTP endpoint validates the request body and token, then enqueues the event. In the background consumer, the outcome status is checked and the condition is evaluated against the full array of event payloads in the outcome's event log. If the condition is satisfied while the outcome is OPEN, it transitions to PENDING. Every accepted event resets settles_at.

Shape

A condition is a flat list of leaves. The list is satisfied when every leaf is satisfied (implicit AND). Empty list is vacuously true.

[
  { "fact": "signed_by_A", "operator": "seen" },
  { "fact": "signed_by_B", "operator": "seen" }
]

Leaf node

A leaf has three fields. fact is the action field of a submitted event. operator decides how the leaf is checked. value is required by operators that need a threshold or a scalar.

{ "fact": "signed_by_counterparty", "operator": "seen" }

The leaf above is satisfied when the event of that action was submitted at least once.

Operators

Sixteen operators in three groups. Pick one per leaf.

Occurrence

OperatorDashboard labelMeaningValue
seenis seenEvent was submitted at least oncenone
not seenis not seenEvent has not been submittednone
count_gteis seen at least N timestotal occurrences ≥ Ninteger
count_lteis seen at most N timestotal occurrences ≤ Ninteger
count_gtis seen more than N timestotal occurrences > Ninteger
count_ltis seen fewer than N timestotal occurrences < Ninteger
count_eqis seen exactly N timestotal occurrences = Ninteger

Count operators do not dedup by action. Every occurrence in the event log is counted.

Comparison

The leaf reads the event's properties.value and compares it to the leaf's value.

OperatorDashboard labelMeaningValue type
matchis equal toproperties.value equals the leaf valuestring, number, or boolean
gteis at leastproperties.value ≥ leaf valuenumber
lteis at mostproperties.value ≤ leaf valuenumber
gtis greater thanproperties.value > leaf valuenumber
ltis less thanproperties.value < leaf valuenumber

Comparison operators dedup by action. Only the latest properties.value for each action is visible to the evaluator.

Missing value allowed

Same as comparison, but the leaf is also satisfied when no event of that action was submitted. Use these for "no bad signal" gates.

OperatorDashboard labelSatisfied when
not gteis missing or has value belowevent missing OR properties.value < leaf value
not lteis missing or has value aboveevent missing OR properties.value > leaf value
not gtis missing or has value at mostevent missing OR properties.value ≤ leaf value
not ltis missing or has value at leastevent missing OR properties.value ≥ leaf value

Example. csat not lte 3 is satisfied if no CSAT event was submitted, or every CSAT event had a value greater than 3.

{ "fact": "csat", "operator": "not lte", "value": 3 }

Negative signals

Use not seen and the not * operators for "no bad signal" gates.

[
  { "fact": "agent_replied", "operator": "seen" },
  { "fact": "escalated", "operator": "not seen" },
  { "fact": "reopened", "operator": "not seen" },
  { "fact": "csat", "operator": "not lte", "value": 3 }
]

If the customer disappears and no CSAT is submitted, the last leaf stays true. The outcome can confirm after the settlement period.

properties.attribution is separate from condition evaluation. Use properties.value to gate the outcome. Use properties.attribution when you need a billing quantity.

Examples

Single event

The outcome resolves when "downloaded" is submitted.

[{ "fact": "downloaded", "operator": "seen" }]

All parties must sign

[
  { "fact": "signed_by_buyer", "operator": "seen" },
  { "fact": "signed_by_seller", "operator": "seen" }
]

Signed but not revoked

[
  { "fact": "signed", "operator": "seen" },
  { "fact": "revoked", "operator": "not seen" }
]

Value matches a specific result

[{ "fact": "inspection", "operator": "match", "value": "pass" }]

Submitted as:

{ "key": "...", "action": "inspection", "properties": { "value": "pass" } }

Minimum score threshold

[{ "fact": "rating", "operator": "gte", "value": 4 }]

Submitted as:

{ "key": "...", "action": "rating", "properties": { "value": 4.8 } }

Per-unit billing for metered usage

For metered pricing, the condition just checks that the event occurred. Set properties.attribution to the quantity to bill.

[{ "fact": "api_call", "operator": "seen" }]

Submitted as:

{ "key": "acme:api:nov", "action": "api_call", "properties": { "attribution": 1.2 } }

With price_per_unit: 10, this bills 10 × 1.2 = $12. The agent's attribution_method setting decides which properties.attribution value is used when multiple events arrive.

Multi-step verification

[
  { "fact": "identity_check", "operator": "match", "value": "verified" },
  { "fact": "credit_score", "operator": "gte", "value": 700 },
  { "fact": "document_signed", "operator": "seen" }
]

Seen at least N times

[{ "fact": "warning", "operator": "count_gte", "value": 3 }]

Empty conditions

[] is vacuously true. The outcome resolves on the first event submission.

How evaluation works

On every POST /v1/events, the request body and token are validated immediately and the event is enqueued. In the background consumer:

  1. All existing event log entries for the outcome are fetched.
  2. An array of full event payloads is built from those entries, including the new event.
  3. For comparison operators, events are deduplicated by action. Only the latest properties.value for each action is visible.
  4. For count operators, every occurrence is counted. No dedup.
  5. Every leaf is checked. The condition is satisfied when every leaf is satisfied.
  6. If the result is true and the outcome is OPEN, it transitions to PENDING with scheduled_resolution = CONFIRMED.
  7. If the result is false and the outcome is OPEN, it stays OPEN.
  8. If the outcome is PENDING, scheduled_resolution is updated to CONFIRMED or FAILED depending on the result.
  9. The event is recorded and settles_at is reset to the event timestamp plus settlement_period.

Because evaluation uses the full event log, you never need to replay events. The consumer always has the complete picture. Earlier events remain in the log and can still contribute properties.attribution values for billing, depending on attribution_method.

Validation

The condition is validated when a agent contract is created or updated. Invalid input returns a VALIDATION_ERROR with details pointing to the exact path.

// invalid: leaf uses wrong field name
[{ "type": "signed", "operator": "seen" }]

// valid
[{ "fact": "signed", "operator": "seen" }]

A leaf must include fact and operator. Operators in the comparison and count groups must include value. The seen and not seen operators must not include a value.

On this page