Operator

Payment attempts

GET /api/payment-attempts lists recorded payment attempts, including uncertain settlements; POST …/:id/resolve records an operator verdict.

GET/api/payment-attemptsBearer adminToken

Lists recorded payment attempts, newest first, whether settled or not. It requires the same authentication as receipts. Use it to reconcile uncertain settlements, which leave no receipt even though funds may have moved. For the whole purchase, with its backend call and receipt, read the transaction.

Query

limitintegerdefault: 50
Values above 500 are capped at 500; zero or less returns 400 INPUT_INVALID.
uncertainbooleandefault: false
true lists only attempts marked settlement-uncertain or legacy failed. Values other than true or false return 400 INPUT_INVALID.
curl -s "http://localhost:8080/api/payment-attempts?uncertain=true" \
  -H "Authorization: Bearer $ADMIN_TOKEN"

Payment attempt

id / requestId / resourceIdstringrequired
provider / replayKeystringrequired
statusstringrequired
reserved, verified, settled, rejected, settlement-uncertain, or failed (legacy). The pipeline no longer writes this status.
amount / currencystringrequired
payer / payeestring
externalReferencestring
The transaction hash, when available. A missing hash does not prove that no funds moved.
rejectionReasonstring
Why the attempt was rejected or left uncertain.
transactionIdstring
The purchase's journal entry. Absent on attempts written before 2.5.0.
resolutionobject
Records a resolution made after the request ended: method (reconcile or manual), actor, reason, at, and when available externalReference (unverified for manual) and public chain evidence. Later pipeline writes cannot overwrite the resolution.
createdAt / updatedAtISO 8601required
Check each uncertain attempt on chain before asking the buyer to pay again. Its authorization remains reserved, so the same proof cannot be reused. agent-commerce reconcile reads the chain evidence for you; doctor reports the count when it is nonzero.

Resolve an attempt

POST/api/payment-attempts/{id}/resolveBearer adminToken

Records an operator verdict, with the same rules as agent-commerce resolve: only reserved, settlement-uncertain and legacy failed attempts, once each. The admin token controls access but does not identify the operator, so the body names an actor. With AP2 enabled, the gateway also tries to consume or release the attempt's mandate hold.

Body

statussettled | rejectedrequired
actorstringrequired
A caller-supplied label for the operator, at most 256 characters.
reasonstringrequired
At most 1024 characters.
externalReferencestring
An unverified reference, such as a transaction hash from an explorer, at most 256 characters. It stays in the resolution metadata and does not replace the attempt's recorded transaction ID.
confirmExpiredboolean
Required to reject an attempt with no valid recorded expiry.

Bodies over 4 KiB return 413 and malformed JSON returns 400, both in the framework's format (see Errors). Valid JSON that is not an object, an unknown field, a wrong type, another status, or a blank or overlong text field returns 400 INPUT_INVALID. None of these writes anything.

curl -s -X POST "http://localhost:8080/api/payment-attempts/attempt_7c1e…/resolve" \
  -H "Authorization: Bearer $ADMIN_TOKEN" \
  -H "content-type: application/json" \
  -d '{"status":"settled","actor":"[email protected]","reason":"transfer seen in explorer","externalReference":"0x4f2c…9ab1"}'

hold appears only for an attempt with an AP2 hold; with written: false, reason explains why the journal still shows it as pending. A later reconcile run can retry it when AP2 is enabled. A refused resolution returns 409 INPUT_INVALID with details.reason and details.attemptStatus:

details.reasonMeaning
already_resolvedthe attempt already has a resolution, possibly from a concurrent resolver
not_resolvablethe attempt is in a final status, or its status changed without a resolution
too_recenta reserved attempt younger than 15 minutes; the gateway may still be settling it
not_expiredrejected while the recorded expiry is still in the future
expiry_unknownrejected without a valid recorded expiry; resend with confirmExpired: true

An unknown attempt id is 404 RESOURCE_NOT_FOUND.