Create your first ActionReceipt.
Run one fixed action. Bulla writes a portable transaction file you can keep, check locally, and compare with your application’s action log.
This is a constructed local example. It records a payment request but does not contact a payment network or establish whether money moved.
Limits of this constructed example
Bulla can find actions in the supplied action log that have no matching receipt. It cannot find actions missing from that log.
Technical evidence
Coverage is computed relative to the supplied denominator; a compromised or incomplete independent log narrows what reconciliation can find. The browser recomputes digests and the set difference over unsigned fixture receipts and does not verify signer identity or the denominator's independence.
1. Run the first action
Install Bulla and run the fixed example. Bulla creates an output directory containing the receipt, the receiving system’s action log, and reports you can inspect.
$ python -m pip install "bulla==0.47.1"
$ bulla demo --out first-action
ACTION
recorded action payments.charge
amount USD 125.00
declared limit USD 200.00
OMISSION CONTROL
coverage before 1/1
coverage after 1/2
unreceipted action pay_demo_043About the published package
The command shown here comes from the package published on PyPI, not from unreleased repository code.
Technical evidence
Commands labelled published are captured from the exact artifact accepted by PyPI; repository source is not substituted for it.
The command refuses an existing output path. Run it without --out to use a new preserved private temporary directory.
2. Inspect the retained artifacts
Your application does not need to hand-write JSON. Bulla creates receipts/pay_demo_042.json when the application records the action. The receiving system’s final action log contains two actions; only the first has a receipt.
{
"generated_by": "bulla demo 0.47.1",
"receipt": {
"action": {
"outcome": {
"result_hash": "sha256:2547d779732c4e111eb7b58289cbce03623b8e6e1ee61874d6c5982d2e7806ce",
"status": "ok"
},
"subject": {
"amount_minor": 12500,
"currency": "USD",
"event_id": "pay_demo_042"
},
"type": "payments.charge"
},
"mandate": {
"authority": {
"delegation": [],
"policy": "policy://constructed-payment-v1",
"principal": "did:web:example.test#agent"
},
"bounds": {
"rollback_window": "P7D",
"scope": "payments.charge amount_minor<=20000 currency=USD"
}
},
"evidence_refs": [
{
"grounding": "self_asserted",
"hash": "sha256:2547d779732c4e111eb7b58289cbce03623b8e6e1ee61874d6c5982d2e7806ce",
"name": "constructed_receiver_record"
}
]
},
"receiver_actions": [
{
"amount_minor": 12500,
"currency": "USD",
"id": "pay_demo_042",
"kind": "payments.charge",
"receiver": "constructed-local-receiver",
"record_sha256": "sha256:2547d779732c4e111eb7b58289cbce03623b8e6e1ee61874d6c5982d2e7806ce"
},
{
"amount_minor": 5000,
"currency": "USD",
"id": "pay_demo_043",
"kind": "payments.charge",
"receiver": "constructed-local-receiver",
"record_sha256": "sha256:a5c37224918e0a97298938a9218a739572d98a7ba9fb807d6c88f9de691f1e02"
}
]
}The JSON file is the portable transaction record. In a real integration, the customer or next system keeps its own copy.
3. Verify the receipt and the altered control
Check the original receipt, then check a copy whose amount was changed. Bulla reports the file checks separately from claims that need evidence from another system.
$ bulla receipt drill first-action/receipts/pay_demo_042.json --format json
$ bulla receipt drill first-action/controls/pay_demo_042.amount-changed.json --format json
# original: exit 0 · record integrity VERIFIED
# changed copy: exit 1 · record integrity FAILEDThe changed copy says USD 125.01 but still carries commitments for the original USD 125.00 record, so the integrity check fails. The original file is unchanged. You can run the same sequence in the homepage example.
What the receipt check does not prove
This checks the receipt file. Evidence from the systems that performed or observed the action is still needed to establish what happened.
Technical evidence
Verification establishes only the reported digest, identity, or inclusion depth; it does not establish the truth of the underlying process.
4. Find the action with no receipt
Checking a receipt cannot reveal an action that has no receipt. Bulla therefore compares the receipt files with a separate action log from the receiving system. That log contains a second action with no matching receipt.
{
"before": {
"coverage": "1/1",
"unreceipted": []
},
"after": {
"coverage": "1/2",
"unreceipted": [
"pay_demo_043"
]
},
"original_receipt_integrity": "VERIFIED"
}The receipt check found a changed file. The action-log comparison found a missing receipt. The comparison is only as complete as the action log you supply; Bulla cannot find actions missing from that log.
5. Keep a checker with the receipt
The verification kit contains the receipt specification, a small checker, and known examples. Keep it with important records when you want a verifier that does not depend on a hosted dashboard.
$ bulla receipt drill first-action/receipts/pay_demo_042.json
$ bulla receipt kit --out action-receipt-v0.2-verification-kit.zip
$ curl -fsSLO https://glyphstandard.com/downloads/bulla/0.47.1/action-receipt-v0.2-verification-kit.zip.sha256
$ shasum -a 256 -c action-receipt-v0.2-verification-kit.zip.sha256Archive SHA-256: 8f2cdd16bcbd1a1121f49545b6a6512872b188221ca30ec054dfd6b2fb2142ab. The drill checks that Bulla and the included zero-dependency checker return the same result without network access.
Download the verification kit or download its detached digest.
6. Add Bulla where your application acts
Put the wrapper in the API gateway, tool router, payment handler, or agent runtime that authorizes or sends the action. The model itself does not need to know about Bulla. Keep the receiving system’s action log separately when you want to find actions with no receipt.
from bulla import event_coverage, observed_record_sha256, wrap_action
observed = []
receipts = []
record = {"id": "pay-1", "kind": "payments.charge", "amount_minor": 12500}
record["record_sha256"] = observed_record_sha256(record)
with wrap_action("payments.charge", {"event_id": "pay-1", "amount_minor": 12500}) as act:
observed.append(record)
# dispatch the consequential action here
act.add_evidence("receiver_action_record", record["record_sha256"], "self_asserted")
act.set_result(record["record_sha256"])
receipts.append(act.receipt)
report = event_coverage(observed, receipts, anchor="receiver-action-record")Continue with the Python SDK guide, the deeper coverage walkthrough, or the buyer requirement for vendors.