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
| Operator | Dashboard label | Meaning | Value |
|---|---|---|---|
seen | is seen | Event was submitted at least once | none |
not seen | is not seen | Event has not been submitted | none |
count_gte | is seen at least N times | total occurrences ≥ N | integer |
count_lte | is seen at most N times | total occurrences ≤ N | integer |
count_gt | is seen more than N times | total occurrences > N | integer |
count_lt | is seen fewer than N times | total occurrences < N | integer |
count_eq | is seen exactly N times | total occurrences = N | integer |
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.
| Operator | Dashboard label | Meaning | Value type |
|---|---|---|---|
match | is equal to | properties.value equals the leaf value | string, number, or boolean |
gte | is at least | properties.value ≥ leaf value | number |
lte | is at most | properties.value ≤ leaf value | number |
gt | is greater than | properties.value > leaf value | number |
lt | is less than | properties.value < leaf value | number |
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.
| Operator | Dashboard label | Satisfied when |
|---|---|---|
not gte | is missing or has value below | event missing OR properties.value < leaf value |
not lte | is missing or has value above | event missing OR properties.value > leaf value |
not gt | is missing or has value at most | event missing OR properties.value ≤ leaf value |
not lt | is missing or has value at least | event 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:
- All existing event log entries for the outcome are fetched.
- An array of full event payloads is built from those entries, including the new event.
- For comparison operators, events are deduplicated by
action. Only the latestproperties.valuefor each action is visible. - For count operators, every occurrence is counted. No dedup.
- Every leaf is checked. The condition is satisfied when every leaf is satisfied.
- If the result is
trueand the outcome isOPEN, it transitions toPENDINGwithscheduled_resolution = CONFIRMED. - If the result is
falseand the outcome isOPEN, it staysOPEN. - If the outcome is
PENDING,scheduled_resolutionis updated toCONFIRMEDorFAILEDdepending on the result. - The event is recorded and
settles_atis reset to the event timestamp plussettlement_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.