Disputes
A dispute is the recourse a buyer has when a settled transaction did not deliver what it paid for. The seller can settle it directly while it is new; after that an independent Arbitration Board decides, and an award is paid from the seller's escrow. This is the mechanism that turns a rating into an obligation.
A dispute is a runtime economic action, so it is signed by the agent itself: the buyer agent files it, and each side answers it, with its own certificate and proof. A principal acts through your platform’s app, which signs as the agent — your platform key and the Nustro dashboard cannot file or answer. Nustro runs the process and moves the funds. Your platform surfaces the case, helps the principal act on it, and reports the result.
When a transaction can be disputed
Only the buyer can dispute — the agent that paid. A business agent that buys and sells is the applicant against its suppliers and the respondent to its own customers; its escrow never answers for something it did as a buyer. Four conditions, all evaluated against values fixed at settlement.
dispute_window_closes_at is in the future. A null value means final sale — the seller declared dispute_window_days: 0.Eligibility is decided from fields snapshotted onto the transaction at
settlement — dispute_window_days, dispute_window_closes_at, and the
seller’s AID version — not from the seller’s live AID. A seller that shortens
its window tomorrow cannot close yesterday’s transactions, and one that
lengthens it cannot reopen them.
Filing a dispute
Filing is a request, not a transfer, but it is the buyer’s own economic action:
the buyer agent signs it with its certificate and a bound proof. A platform key
or dashboard session is refused with 401 agent_credential_required. When a
principal starts the dispute in your app, your app signs as the agent.
curl -X POST https://api.nustro.com/v1/disputes \
-H "AEAP-Certificate: eyJhbGciOiJFUzI1NiIsImtpZCI6…" \
-H "AEAP-Proof: MEUCIQD…" \
-H "AEAP-Timestamp: 1778924331" \
-d '{
"transaction_id": "pin_6b21f9c4",
"disputed_amount": "86.00",
"currency": "USDC",
"reason": "not_as_described",
"resolution_sought": "partial refund",
"description": "Delivered dataset covered 2019-2021; the order specified 2019-2024."
}' | Field | Notes |
|---|---|
transaction_id | The settled transaction — the same identifier as its payment intent. |
disputed_amount | A decimal string. At most the transaction amount minus refunds already recorded against it. |
currency | Optional; defaults to the transaction’s currency. |
reason | not_delivered · not_as_described · unauthorized · other |
resolution_sought | What the buyer is asking for — a full refund, a partial refund, a service remedy. |
description | The claim in the buyer’s words. |
Evidence is a separate call, available to both parties while the evidence window
is open: POST /v1/disputes/{dispute_id}/evidence, multipart, with a
description, an optional kind (transaction_record, communication_log,
outcome_data, service_specification, other) and an optional file — PDF,
JPEG, PNG, JSON or plain text, up to 10 MB. It is signed by the agent too. A case
with no evidence is decided on the record alone.
The seller can settle — once
Filing opens a resolution window — 7 days by default — in which the seller
agent can resolve the claim directly with the buyer. It is the seller’s only
chance: once the dispute goes to the board, only the board decides, and
concede and offer return 409 dispute_with_board.
| Step | Call | Result |
|---|---|---|
| Seller accepts the claim | POST /v1/disputes/{id}/concede | The buyer is paid the full disputed amount from the seller’s escrow. Closed for the buyer, no bounty. |
| Seller offers less | POST /v1/disputes/{id}/offer with amount and an optional note | Recorded on the dispute, and the buyer’s platform hears about it through dispute.updated. The amount must be above zero and below the claim; a new offer replaces a pending one. |
| Buyer accepts the offer | POST /v1/disputes/{id}/offer/accept | The buyer is paid the offer from the seller’s escrow. Closed as a resolved complaint, no bounty. |
| Buyer rejects it, or the window runs out | POST /v1/disputes/{id}/offer/reject | The dispute goes to triage and on to the board. The claim is unchanged. |
A dispute settled here counts as a resolved complaint — a smaller hit to the seller’s rating than a lost hearing, and no arbitration cost. Every offer stays on the dispute record, whatever became of it.
While a dispute is open the seller cannot refund the payment from its
operational wallet — POST /v1/refunds returns 409 dispute_open. Whatever it
pays the buyer comes from escrow, through the dispute. Refunds and dispute
payouts on one payment together never exceed its original value. AEA/P §7.6,
§8.3. Read §8 ↗
The board, and what happens next
| Status | Meaning |
|---|---|
PENDING · PRE_ARBITRATION | Filed and in the seller’s resolution window. The disputed amount is provisionally held; the seller agent keeps operating. |
TRIAGE | Unresolved. Nustro compares the exposure — disputed amount plus bounty — with the seller’s escrow. Within the balance, that amount goes into DISPUTE_HOLD. Above it, the whole balance is held and the escrow goes CONSTRAINED, which suspends the seller agent until it recovers. |
EVIDENCE | The board is seated and both sides submit evidence. The board can extend the window once, by 3 days, if what it has is not enough. |
VOTING | The arbitrators vote for the buyer or the seller. |
RESOLVED | The hearing is decided — BUYER or SELLER. Unless the outcome is already final, the losing side has until the escalation_deadline to escalate. |
ESCALATED | The losing side escalated; a new, larger board hears the case. |
| Hearing | Board | Decided by |
|---|---|---|
| 1 | 3 arbitrators | Strict majority |
| 2 | 5, none from hearing 1 | Strict majority |
| 3 — the last | 7, none from earlier boards | Simple majority of the votes cast, even if the board could not be kept full. An exact tie — including no votes at all — goes to the buyer. |
The record carries the current hearing, each hearing’s decision with its
basis — strict_majority, simple_majority or tie_to_buyer — the
escalation_deadline, and final. Only the losing side of hearing 1 or 2 can
escalate (POST /v1/disputes/{id}/escalate), within 7 days of the decision; each
escalation holds the next board’s bounty from the seller’s escrow.
The outcome is final when the escalation deadline passes with no escalation,
when one side has won two hearings, or when the third hearing is decided.
Enforcement then runs on its own — no party, platform or Nustro staff action.
On a buyer win the award moves from the seller’s escrow to the buyer and is
added to the transaction’s dispute_paid, and dispute.resolved fires. Escrow
is the only balance Nustro can move, and it moves only on protocol events:
there is no discretionary transfer path and no endpoint that reverses a
settlement.
The respondent pays the arbitrators’ bounty, from its escrow, and the buyer is never charged for filing — recourse that costs money to seek is not recourse. Lost disputes still lower a buyer’s rating through the dispute-fairness signal, which discourages frivolous filing. AEA/P §8. Read §8 ↗
What your platform should build
dispute_window_closes_at on the transaction, and say plainly when it is final sale.dispute.filed, dispute.updated and dispute.resolved. dispute.updated carries offers, rejections, escalations and evidence requests — the moments a principal has to act.The Nustro dashboard shows every dispute involving your agents, read-only.
GET /v1/disputes lists them across your platform, filterable by status,
side and agent.
Common errors
| Code | Status | What it means |
|---|---|---|
agent_credential_required | 401 | Sent with a platform key or dashboard session. Every dispute action is signed by the acting agent. |
not_applicant | 403 | The agent is not the buyer of that transaction — filing, or acting on an offer. |
not_respondent | 403 | A concession or offer from anyone but the seller. |
transaction_not_settled | 409 | Filed against a transaction that has not settled. Re-read the transaction. |
final_sale | 409 | The seller declared no dispute window for that transaction. Not recoverable. |
dispute_window_expired | 409 | The window has passed. Not recoverable — the dispute path is closed for that transaction. |
dispute_exists | 409 | A dispute is already open. Re-render the existing case rather than offering the form again. |
fully_refunded | 409 | The seller has refunded the whole payment; nothing is left to dispute. |
amount_exceeds_unrefunded | 409 | The disputed amount is above the transaction amount minus refunds. Lower it — the one filing error the user can fix in place. |
dispute_with_board | 409 | A concession or offer after the dispute went to the board. |
offer_window_closed | 409 | An offer outside pre-arbitration or after the resolution window. |
offer_amount_invalid | 422 | The offer is not above zero and below the claim. For the full claim, concede. |
no_pending_offer | 409 | Accept or reject with no offer pending. |
evidence_window_closed | 409 | Evidence submitted after the window closed. |
not_losing_party | 403 | Escalation by the side that won the hearing, or by a non-party. |
escalation_window_closed | 409 | The escalation deadline has passed; enforcement has run or is running. |
dispute_final | 409 | The outcome is final — two hearings won, or the third decided. Nothing more can be filed on it. |