Documentation

Read the rulebook before you write one

Two endpoints do most of the work. Everything else is a rulebook you write once.

The sandbox runs this exact engine against real rulebooks today. Card issuing goes live at launch — reserve your place.

Authentication

One base URL, one bearer key. Sandbox keys are free at signup; live keys at launch.

# base URL https://api.getappen.com # every request Authorization: Bearer sk_test_…

Quickstart

Three calls from nothing to a funded card.

# 1 — create a rulebook POST /v1/rulebooks { "name": "field-techs", "rules": [ { "kind": "merchant_lock", "config": { "allow": ["ferguson.com"] } }, { "kind": "amount_ceiling", "config": { "max_cents": 100000 } }, { "kind": "approval_threshold", "config": { "above_cents": 50000 } } ] } → { "id": "rb_8f2a", "rules": 3 } # 2 — declare what you are about to buy POST /v1/intents { "rulebook": "rb_8f2a", "merchant": "ferguson.com", "amount": 78000 } → { "id": "int_31c9", "decision": "review" } # 3 — a person approves; the card is created and funded POST /v1/intents/int_31c9/approve → { "decision": "approve", "card": { "id": "crd_77b1", "funded_cents": 78000 } }

Verdicts

ValueMeaningWhat to do
approveEvery rule passedA funded card exists. Proceed.
declineAt least one rule refusedStop. The response names every rule that refused and why.
reviewA human gate trippedWait. Poll the intent or listen for the webhook.
partialApproved at a lower amountRead approved_cents before proceeding.

The rule object

Every rule is a kind and a config. Unknown keys are rejected at write time rather than silently ignored.

{ "kind": "spend_cap", "config": { "max_total_cents": 500000, "window": "week", // "day" | "week" | "month" | seconds "allow_partial": true // approve up to the remaining headroom } }

Branching and loops

condition is a pure if/else — children marked "when": "pass" run when it is true, "when": "fail" when it is false, and a false condition never declines the card. repeat is a while loop over the payment stream: iterations are counted from captured ledger entries, and once the loop is complete it declines.

# branch on amount, then run a 4-payment installment loop [ { "kind": "condition", "config": { "when_amount_over_cents": 10000 }, "children": [ { "kind": "approval_threshold", "when": "pass", "config": { "above_cents": 0 } }, { "kind": "velocity", "when": "fail", "config": { "max_count": 5, "window_sec": 86400 } } ] }, { "kind": "repeat", "config": { "times": 4, "total_cents": 100000, "merchant": "landlord.com" }, "children": [ { "kind": "amount_ceiling", "when": "pass", "config": { "max_cents": 25000, "allow_partial": true } } ] } ]

Webhooks

Two directions. We call you to ask, and we call you to tell.

# we ask — webhook_check, before any funding POST https://you.example.com/appen { "intent": "int_31c9", "merchant": "ferguson.com", "amount": 78000 } # you answer with whatever your business knows { "decision": "approve" } # we tell — every state change intent.declared   intent.approved   intent.declined intent.review     card.funded     card.closed

The audit trace

Every rule runs on every intent. There is no short-circuit, which is why a decline returns all of its reasons and not just the first.

{ "decision": "decline", "results": [ { "kind": "merchant_lock", "status": "pass", "detail": "'ferguson.com' matches allow-list" }, { "kind": "amount_ceiling", "status": "fail", "detail": "$1,240.00 exceeds $1,000.00 max" }, { "kind": "spend_cap", "status": "pass", "detail": "$310.00 this week of $5,000.00" }, { "kind": "duplicate_charge_guard","status": "fail", "detail": "identical charge 4 minutes ago" } ] }

For agents

An agent gets exit codes it can branch on, and never a card number.

appen buy ferguson.com 780.00 # 0 approved · 1 declined · 2 held for a human · 3 error

agent.txt  ·  llms.txt

Run this in the sandbox All sixteen rules