Enruta

Docs · Objects

Dispute packet

LiveUpdated

Sellers and Finance. Written for merchants that receive agent orders and the finance team that signs the policy and the weekly report.

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

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

What is in it

GroupFieldsMaps to
Identityprincipal_login_id (halvern/dana.reyes@halvernrobotics.com), agent_id, agent_operator, kya_attestation_idCE3.0 login id; Justt transactionCustomerFrontUserId; Mastercard agent identity; Chargeflow additional_notes
Device and networkdevice_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_ipCE3.0 device id, fingerprint, IP
Authorizationmandate_id, mandate_jws, policy_version, amount_authorized, payee_authorized, valid_until, consumed_amount_at_timeThe “payment instructions” in the card rules
Consentconsent_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[]
Approvalapproval_events[]{actor_login_id, method, at, ip}Mastercard first-party-trust additional data
Decisiondecision{action, reason_codes, checks}, deviations[]“The logic of the AI decision”
Orderorder_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)
Railrail_refs{authorization_id, amount_authorized, amount_cleared, tlid, network_txn_id}Authorized versus cleared; Mastercard TLID when present
Historyprior_records[]{record_id, at, amount, merchant, outcome}CE3.0 prior transactions, 120 to 365 days before the disputed one
Reversalsreversals[]{kind, amount, at, status}Chargeflow refunds[]; Ethoca REFUNDED
Sealingrecord_hash, chain_position, merkle_root, rfc3161_timestamp, verify_commandnpx enruta-verify <id>.json
Exportsexports{stripe_evidence_text, shopify_uncategorized_text, chargeflow_payload, justt_payload, mastercard_context, klarna_pdf}, representment_pdf_url, jws_urlDispute 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.
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

SurfaceCallWho
RESTGET /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}/outcomeDeveloper, auditor; agents list their own
REST, sellersGET /v0/sellers/records/{buyer_record_id}/dispute-packet, GET /v0/sellers/records/{id}/representment.pdf, GET /v0/sellers/disputesSeller key
MCPlist_disputesThe agent
SDKdisputes.list, .get, .packet(recordId), .representmentPdf(recordId), .recordOutcome(id, body)
CLIenruta 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.