# Dispute packet

One file generated from a sealed record that gives a merchant, an issuer or a dispute vendor the agent identity, the mandate, the approval chain and the cardholder-recognizable login id, in the formats they accept.

Status: Live · Updated: 2026-09-13
For: seller, finance
Source: https://enruta.ai/docs/dispute-packet

No dispute interface has a field for an agent, a mandate or an approval. The dispute packet fills the free-text and file slots that exist with material that verifies: a signed JWS for machines, a one-page representment PDF for people, and six exports shaped for the tools merchants already use.

## Why the agent side holds the evidence

Visa CE3.0 (effective 2026-10-24) counts four elements: the login id, a device id or fingerprint, an IP address, and the shipping address, plus two prior undisputed transactions 120 to 365 days old. Three of the four live naturally on the agent side (the principal’s login id, the OAuth or API session’s device, the agent’s egress IP and the approver’s IP); the merchant holds only the address and the item description. The packet brings both halves together.

## State

```text
drafted ─▶ exported ─▶ submitted ─▶ outcome_recorded   (outcome: won | lost | withdrawn)
```

## What is in it

| Group | Fields | Maps to |
|---|---|---|
| Identity | `principal_login_id` (`halvern/dana.reyes@halvernrobotics.com`), `agent_id`, `agent_operator`, `kya_attestation_id` | CE3.0 login id; Justt `transactionCustomerFrontUserId`; Mastercard agent identity; Chargeflow `additional_notes` |
| Device and network | `device_id` (≥ 15 chars: a hash of the OAuth connection or API key), `device_fingerprint` (≥ 20 chars: a hash of the approving session’s user agent and IP), `approver_public_ip`, `agent_egress_ip` | CE3.0 device id, fingerprint, IP |
| Authorization | `mandate_id`, `mandate_jws`, `policy_version`, `amount_authorized`, `payee_authorized`, `valid_until`, `consumed_amount_at_time` | The “payment instructions” in the card rules |
| Consent | `consent_record_id`, `merchant_policies[]{policy_type, policy_hash, consented_at, acceptance_method}` | Visa Core Rules 4.1.24.6 and 4.1.24.8; Chargeflow `policies[]` |
| Approval | `approval_events[]{actor_login_id, method, at, ip}` | Mastercard first-party-trust additional data |
| Decision | `decision{action, reason_codes, checks}`, `deviations[]` | “The logic of the AI decision” |
| Order | `order_ref{protocol, order_id, client_reference_id, checkout_session_id}`, `catalog_snapshot_hash`, `line_items[]`, `shipping_address`, `order_confirmation{url, available_until}` | ACP `client_reference_id`; UCP order; Visa Core Rules 4.1.24.8 (confirmation available 120 days) |
| Rail | `rail_refs{authorization_id, amount_authorized, amount_cleared, tlid, network_txn_id}` | Authorized versus cleared; Mastercard TLID when present |
| History | `prior_records[]{record_id, at, amount, merchant, outcome}` | CE3.0 prior transactions, 120 to 365 days before the disputed one |
| Reversals | `reversals[]{kind, amount, at, status}` | Chargeflow `refunds[]`; Ethoca `REFUNDED` |
| Sealing | `record_hash`, `chain_position`, `merkle_root`, `rfc3161_timestamp`, `verify_command` | `npx enruta-verify <id>.json` |
| Exports | `exports{stripe_evidence_text, shopify_uncategorized_text, chargeflow_payload, justt_payload, mastercard_context, klarna_pdf}`, `representment_pdf_url`, `jws_url` | [Dispute packet exports](https://enruta.ai/docs/dispute-packet-exports) |

## The CE3.0 checklist

`ce30 { elements_present, elements_missing, history_available, note }` says which of `login_id`, `device`, `ip`, `shipping_address` and `history` the packet carries and why any is missing. Records sealed since September 2026 have no prior transactions yet, so `history_available` is `false` on every packet until the first half of 2027; the packet says so instead of pretending.

## Signature, PDF, record

- **JWS.** The packet without its exports is signed as a compact JWS with the Enruta seal key, `typ: "enruta-dispute-packet+jwt"`.
- **Representment PDF.** One or two Letter pages titled “Representment: agent purchase under a signed mandate”, sections Identity, Authorization, Approval, Order, Rail, Sealing, Verify. Text only, no images, generated without third-party libraries so it renders the same everywhere.
- **Record.** A linked record `kind: dispute_packet`, `chain: dispute_of` the payment record, `subject` = the packet without exports and without the mandate JWS (the JWS is in `artifacts.mandate`), `strength.subject: customer_attested`. When the seller generates it, the record goes on the seller’s chain with `chain.of` the buyer’s record and its hash.
- **Outcome.** `POST /v0/disputes/{id}/outcome` with `won`, `lost` or `withdrawn` seals a `dispute_outcome` record chained to the packet, patches `dispute_afterlife` in the original record’s bundle (never its canonical bytes) and emits `dispute.outcome_recorded`.

```json
GET /v0/records/rec_01M25129GZMJXTNQ47TMZRDGHN/dispute-packet

{
  "id": "dsp_01M2H1K8Q3W7T2N9B5D4H6XRC",
  "state": "drafted", "generated_by": "owner",
  "packet": {
    "principal_login_id": "halvern/dana.reyes@halvernrobotics.com",
    "agent_id": "agt_procurement_02", "agent_operator": "Halvern Robotics, Inc.", "kya_attestation_id": "kya_01M2F1R6W2T9K4Q7N3B8D5H1XS",
    "device_id": "3f9c1e7a2b8d4c6e0f1a", "device_fingerprint": "9a1c…(sha256)", "approver_public_ip": null, "agent_egress_ip": "203.0.113.24",
    "mandate_id": "mnd_01M2512M8FQ7ZK4T1V9W3N6BXC", "policy_version": 4, "amount_authorized": "24.98", "payee_authorized": "reamandrule.com",
    "decision": { "action": "authorized", "reason_codes": [], "checks": ["payee_allowlist", "per_payment_cap", "cumulative_budget_atomic", "time_window", "category", "duplicate_idempotency", "sanctions_screen"] },
    "order_ref": { "protocol": "ucp", "order_id": "ord_01M2512SM3V3A9R4EBJM8RRRV5", "checkout_session_id": "chk_01M2511XQ4N7B8T2R6H9K3W5MD" },
    "rail_refs": { "authorization_id": "iauth_1S7Kq2…", "amount_authorized": "24.98", "amount_cleared": "24.98" },
    "prior_records": [], "reversals": [{ "kind": "refund", "amount": "9.99", "at": "2026-09-13T15:04:02Z", "status": "completed" }],
    "sealing": { "record_hash": "7e2a…c41d", "chain_position": 12, "rfc3161_timestamp": "2026-09-10T17:42:08Z", "verify_command": "npx enruta-verify rec_01M25129GZMJXTNQ47TMZRDGHN.json" }
  },
  "ce30": { "elements_present": ["login_id", "device", "ip"], "elements_missing": ["shipping_address", "history"], "history_available": false,
            "note": "No records for this principal 120 to 365 days before the transaction; the checkout carried no shipping address." },
  "jws": "eyJ0eXAiOiJlbnJ1dGEtZGlzcHV0ZS1wYWNrZXQrand0Iiwi…",
  "exports": { "stripe_evidence_text": "Agent purchase under a signed mandate\n…", "chargeflow_payload": { … }, "justt_payload": { … }, "mastercard_context": { … } },
  "packet_record_id": "rec_01M2H1KB7T3W9Q2K6N4B8D1H5XR"
}
```

## Interfaces

| Surface | Call | Who |
|---|---|---|
| REST | `GET /v0/records/{id}/dispute-packet`, `POST …/dispute-packet` (regenerate), `GET /v0/records/{id}/representment.pdf`, `GET /v0/disputes`, `GET /v0/disputes/{id}`, `POST /v0/disputes/{id}/outcome` | Developer, auditor; agents list their own |
| REST, sellers | `GET /v0/sellers/records/{buyer_record_id}/dispute-packet`, `GET /v0/sellers/records/{id}/representment.pdf`, `GET /v0/sellers/disputes` | Seller key |
| MCP | `list_disputes` | The agent |
| SDK | `disputes.list`, `.get`, `.packet(recordId)`, `.representmentPdf(recordId)`, `.recordOutcome(id, body)` |  |
| CLI | `enruta disputes list \| packet <rec> [--pdf out.pdf] [--export stripe\|shopify\|chargeflow\|justt\|mastercard]` |  |
| Console | `/disputes`, `/disputes/{id}`: the checklist, copy buttons for each export, the PDF, the outcome form; “Generate dispute packet” on `/payments/{id}`; the seller’s order page |  |

Pricing: on the merchant side a packet is $5 beyond the plan’s allowance, per packet, never per outcome. Enruta makes no promise about win rates; the packet is the first material a merchant can submit for an agent order at all.
