AI payment failure investigator: why did this payment fail?
An agent reads a payment, its attempts and the processor status page, then explains the cause and the next step for your support team.
- Files
- Read-only connection
- Web
- Callback
- Budget cap
Watch a real run
A real run on Oct 7, 2026, replayed. Model: openai/gpt-6-luna.
{
"payment_id": "pay_123"
}- Queuedbudget $0.04 reserved
- MicroVMmicroVM ready
- MicroVMworker started · shell off · web on
- File
read playbook.md - Connectionpayments GET /payments/pay_123
- Result
payments GET → 200 - Connectionpayments GET /payments/pay_123/attempts
- Result
payments GET → 200 - MicroVMmicroVM destroyed
- Succeeded4 model calls, 6,787 tokensRecorded output (English)
Cause: The card issuer declined the payment for insufficient funds.
Evidence:failure_code=insufficient_funds;failure_message=The card issuer declined the charge: insufficient funds.Both attempts failed for the same reason.
Next step: Suggest the customer retry or use another card. - Worker destroyed$0.004055 charged, $0.035945 released
Run it yourself
Open the sample in the playground with the whole request filled in. It runs on your own account, within the budget shown, against the same read-only sample systems.
Budget for this sample: $0.04
How it works
Give it a payment ID and a short playbook. It calls your payments API with a read-only connection, checks the status page when the failure looks like an outage, and answers in six lines. It cannot refund or retry anything because the connection only allows GET.
What the agent can and cannot do
The permissions are part of the request. Anything not listed here is not available to the run.
| Setting | Allowed |
|---|---|
| Connections | payments: GET only, at ephemer.al/api/samples/payments-api |
| Web | Only ephemer.al |
| Shell | Off |
| Files | playbook.md (read-only) |
| Zero data retention | Off: the record is kept for 30 days |
| Budget | $0.04, a hard cap |
| Timeout | 90 seconds |
Credentials never enter the machine: a broker adds them to each call and enforces the methods above.
The code
This is the exact request the sample sends. Set EPHEMERAL_API_KEY to a member key of your organization.
curl -X POST $EPHEMERAL_URL/v1/runs \
-H "Authorization: Bearer $EPHEMERAL_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d @request.jsonFiles in the request
Passed inline, so nothing is uploaded first. The agent sees each one as a read-only file.
playbook.md
# Failed payment playbook
You can only read. The payments connection allows GET, and web access is limited to ephemer.al.
1. GET /payments/{payment_id}. Note status, amount, currency, failure_code and failure_message.
2. GET /payments/{payment_id}/attempts. Check whether earlier attempts failed for the same reason.
3. If failure_code is processor_error or timeout, read https://ephemer.al/api/samples/status for incidents around that time.
4. Decide the cause:
- card_declined, insufficient_funds, expired_card: the customer's bank or card. Suggest a retry or another card.
- processor_error, timeout: ours or the processor's. Say whether the status page shows an incident.
- fraud_review: a risk rule held it. Do not suggest bypassing it.
5. Never guess. If a call fails or a field is missing, say so.
Answer in at most six lines: the cause, the evidence (field and value) and the next step for the support agent.
Do not include card numbers or customer email addresses.
Full request body request.json
The same body, with the files inline.
{
"prompt": "Find out why the payment in input.payment_id failed. Follow playbook.md exactly. Use only what the data returns and never guess; if a call fails or a field is missing, say so.",
"input": {
"payment_id": "pay_123"
},
"files": [
{
"path": "playbook.md",
"content": "# Failed payment playbook\n\nYou can only read. The payments connection allows GET, and web access is limited to ephemer.al.\n\n1. GET /payments/{payment_id}. Note status, amount, currency, failure_code and failure_message.\n2. GET /payments/{payment_id}/attempts. Check whether earlier attempts failed for the same reason.\n3. If failure_code is processor_error or timeout, read https://ephemer.al/api/samples/status for incidents around that time.\n4. Decide the cause:\n - card_declined, insufficient_funds, expired_card: the customer's bank or card. Suggest a retry or another card.\n - processor_error, timeout: ours or the processor's. Say whether the status page shows an incident.\n - fraud_review: a risk rule held it. Do not suggest bypassing it.\n5. Never guess. If a call fails or a field is missing, say so.\n\nAnswer in at most six lines: the cause, the evidence (field and value) and the next step for the support agent.\nDo not include card numbers or customer email addresses.\n"
}
],
"connections": {
"payments": {
"type": "http",
"url": "https://ephemer.al/api/samples/payments-api",
"auth": {
"type": "bearer",
"token": "sample_read_key"
},
"allowed_methods": [
"GET"
],
"instructions": "GET /payments/{id} returns a payment (status, amount in cents, currency, failure_code, failure_message, attempt_count). GET /payments/{id}/attempts returns its attempts."
}
},
"web": {
"allow": [
"ephemer.al"
],
"search": false
},
"shell": false,
"timeout_seconds": 90,
"max_cost_micros": 40000
}