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.

Input
{
  "payment_id": "pay_123"
}
Succeededrun_80b0a7e12e1b4ee6bc00299ba70cd933$0.004055 of $0.04
  1. Queuedbudget $0.04 reserved
  2. MicroVMmicroVM ready
  3. MicroVMworker started · shell off · web on
  4. Fileread playbook.md
  5. Connectionpayments GET /payments/pay_123
  6. Resultpayments GET → 200
  7. Connectionpayments GET /payments/pay_123/attempts
  8. Resultpayments GET → 200
  9. MicroVMmicroVM destroyed
  10. 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.

  11. Worker destroyed$0.004055 charged, $0.035945 released
Model $0.000255 · Web $0.00 · Compute $0.0038

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.

SettingAllowed
Connections
payments: GET only, at ephemer.al/api/samples/payments-api
WebOnly ephemer.al
ShellOff
Filesplaybook.md (read-only)
Zero data retentionOff: the record is kept for 30 days
Budget$0.04, a hard cap
Timeout90 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.json

Files in the request

Passed inline, so nothing is uploaded first. The agent sees each one as a read-only file.

playbook.md
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.

request.json
{
  "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
}

Read more

Related samples

esc

Nothing matches “”.