Submit a chargeback's evidence packet to the issuer
const url = 'https://api.aerandil.com/v1/payment-disputes/2489E9AD-2EE2-8E00-8EC9-32D5F69181C0/evidence/submission';const options = { method: 'POST', headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'}, body: '{"packet_id":"2489E9AD-2EE2-8E00-8EC9-32D5F69181C0","submit_with_gaps":false}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request POST \ --url https://api.aerandil.com/v1/payment-disputes/2489E9AD-2EE2-8E00-8EC9-32D5F69181C0/evidence/submission \ --header 'Authorization: Bearer <token>' \ --header 'Content-Type: application/json' \ --data '{ "packet_id": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0", "submit_with_gaps": false }'The house counters the chargeback with the evidence packet the kernel assembled (assemble_dispute_evidence). Refused before anything is asked of the processor when the issuer is not waiting on the house, the deadline has passed, the evidence already went (here or at the processor’s dashboard), the packet named is not the case’s current one, the processor refused this packet before, a source the packet pinned has changed since it was assembled, or the packet has gaps the request does not acknowledge (submit_with_gaps: true, recorded on the packet with who submitted). Then each document is rendered — the folio’s issued invoice as the document the guest was handed, otherwise a PDF statement of the pinned object — uploaded to Stripe (purpose=dispute_evidence, at most 4.5 MB in all), and the dispute updated with the packet’s text fields and the files, submitted. A processor’s refusal is an outcome, not an error: 200 with outcome: refused and Stripe’s words, recorded on the case; the retry is a new packet. A failure Stripe saved under the packet’s key is read back off the dispute: Stripe holding the packet’s files is the submission, holding nothing of it is the refusal (refusal.code: processor_saved_failure, assemble again). The packet is marked sending with its file ids before the dispute update goes, so a retry after a lost answer (a 503, a dropped connection) finishes that packet — the same files under the same key, Stripe’s saved answer recorded — even after Stripe’s webhook has counted the submission. Every processor call is keyed from the packet’s own id. W1 (2026-10-21): a packet with a Visa Compelling Evidence 3.0 section sends it as Stripe’s nested evidence[enhanced_evidence][visa_compelling_evidence_3][…] keys (the booking’s half and two prior charges, each prior’s payment checked against the version it was read at), the signed registration card, the guest’s messages and the key-card taps go as files of their own, and the documents’ pages are counted against the card’s network before anything is uploaded. An inquiry the issuer escalated to a chargeback takes this submission once more (one per phase). Move 12 (2026-10-22): the submission certifies the dispute-fee waiver from facts the kernel holds and stamps it on the case (waiver: covered or not, and the first condition missed) — a whole packet on time, a reason the packet defends (fraudulent, unrecognized, credit_not_processed, product_not_received, duplicate), a card not keyed by hand, Radar’s normal (Stripe’s own word on the disputed charge, read at every submission) with no elevated rating set aside, the account’s chargebacks under 0.5% of its charges over the rolling year (weighed once the year holds 1,000 charges), and room under its cap (25% of the year’s platform fees) for the case’s own two fees, or fewer than five cases taken; a covered case that is lost passes neither fee to the hotel. Move 13: a gap-free packet still unsent 48 hours before the deadline is submitted by the night audit through the same path, as system. 501 unless AERANDIL_STRIPE_ENABLED=1.
Authorizations
Section titled “Authorizations”Parameters
Section titled “Parameters”Path Parameters
Section titled “Path Parameters”The chargeback (payment_dispute) to counter.
Header Parameters
Section titled “Header Parameters”Opaque idempotency token (Stripe-shape). Repeated POSTs with the same key replay the original response within the cache TTL rather than creating a duplicate object.
Request Body
Section titled “Request Body”object
The packet the desk reviewed; refused when another assembly has replaced it.
Submit knowing the packet’s gaps; required when it has any.
Responses
Section titled “Responses”The processor was asked and its answer recorded: the evidence submitted, or the processor’s refusal.
object
object
Move 12 (2026-10-22): the dispute-fee waiver, certified at this submission and stamped on the case — if the case is lost, neither of the network’s fees is passed to the hotel. null when the processor refused the packet.
object
The first condition not met (WAIVER_REFUSALS).
Stripe Radar’s rating of the payment at authorisation, verbatim (outcome.risk_level on the disputed intent’s latest charge, read at certification; never the kernel’s own assessment).
The charges the year must hold before the ratio is weighed (1,000); under it dispute_ratio is never the condition.
The same, in words.
Example
{ "outcome": "submitted", "waiver": { "condition": "evidence_gaps" }}Headers
Section titled “Headers”Always no-store. Object and link state is live data and is never cacheable.
The bearer authenticated but does not hold the scope this operation requires.
Structured error wrapper. Every non-2xx response from any Aerandil endpoint serialises to this shape; the HTTP status carries the failure class (4xx/5xx), the body adds a stable code for programmatic dispatch plus a human-readable message.
object
object
Stable, programmatic error code (e.g. unauthorized, forbidden, not_found, invalid_body, invalid_url, precondition_failed, no_secondary_key). Clients dispatch on code, not on the message (which may be localised or contextualised in future versions).
Request-id correlation handle echoed on the request-id response header — support teams correlate without scraping logs.
Human-readable explanation of the failure.
Per-error structured detail. The shape varies by code (e.g. forbidden_action carries {"action": "..."}; precondition_failed carries {"current_version": N}).
Examplegenerated
{ "error": { "code": "example", "instance_id": "example", "message": "example", "parameters": "example" }}No such object in the caller’s tenant — which is also the answer when it exists in another tenant, by design.
Structured error wrapper. Every non-2xx response from any Aerandil endpoint serialises to this shape; the HTTP status carries the failure class (4xx/5xx), the body adds a stable code for programmatic dispatch plus a human-readable message.
object
object
Stable, programmatic error code (e.g. unauthorized, forbidden, not_found, invalid_body, invalid_url, precondition_failed, no_secondary_key). Clients dispatch on code, not on the message (which may be localised or contextualised in future versions).
Request-id correlation handle echoed on the request-id response header — support teams correlate without scraping logs.
Human-readable explanation of the failure.
Per-error structured detail. The shape varies by code (e.g. forbidden_action carries {"action": "..."}; precondition_failed carries {"current_version": N}).
Examplegenerated
{ "error": { "code": "example", "instance_id": "example", "message": "example", "parameters": "example" }}evidence_already_submitted, dispute_not_awaiting_evidence, evidence_deadline_passed, no_evidence_packet, evidence_packet_superseded, evidence_packet_refused, evidence_packet_stale (assemble again), evidence_gaps_unacknowledged (its parameters.gaps lists them), dispute_not_on_the_rail (the case carries no processor reference), evidence_submission_unconfirmed (Stripe failed the update and the dispute could not be read back; the packet stays on its way), evidence_too_many_pages (W1: the documents render to more pages than the card’s network takes — fewer than 50 in all, 19 for Mastercard or a card whose network is not known; parameters carries page_count, page_limit, card_brand and each document’s pages; nothing reached the processor), or idempotency_in_flight (an earlier request under the same Idempotency-Key is still running).
Structured error wrapper. Every non-2xx response from any Aerandil endpoint serialises to this shape; the HTTP status carries the failure class (4xx/5xx), the body adds a stable code for programmatic dispatch plus a human-readable message.
object
object
Stable, programmatic error code (e.g. unauthorized, forbidden, not_found, invalid_body, invalid_url, precondition_failed, no_secondary_key). Clients dispatch on code, not on the message (which may be localised or contextualised in future versions).
Request-id correlation handle echoed on the request-id response header — support teams correlate without scraping logs.
Human-readable explanation of the failure.
Per-error structured detail. The shape varies by code (e.g. forbidden_action carries {"action": "..."}; precondition_failed carries {"current_version": N}).
Examplegenerated
{ "error": { "code": "example", "instance_id": "example", "message": "example", "parameters": "example" }}invalid_body, evidence_too_large (the documents come to more than Stripe takes), or idempotency_key_mismatch (the Idempotency-Key was used for another request).
Structured error wrapper. Every non-2xx response from any Aerandil endpoint serialises to this shape; the HTTP status carries the failure class (4xx/5xx), the body adds a stable code for programmatic dispatch plus a human-readable message.
object
object
Stable, programmatic error code (e.g. unauthorized, forbidden, not_found, invalid_body, invalid_url, precondition_failed, no_secondary_key). Clients dispatch on code, not on the message (which may be localised or contextualised in future versions).
Request-id correlation handle echoed on the request-id response header — support teams correlate without scraping logs.
Human-readable explanation of the failure.
Per-error structured detail. The shape varies by code (e.g. forbidden_action carries {"action": "..."}; precondition_failed carries {"current_version": N}).
Examplegenerated
{ "error": { "code": "example", "instance_id": "example", "message": "example", "parameters": "example" }}No card rail is configured on this deployment.
Structured error wrapper. Every non-2xx response from any Aerandil endpoint serialises to this shape; the HTTP status carries the failure class (4xx/5xx), the body adds a stable code for programmatic dispatch plus a human-readable message.
object
object
Stable, programmatic error code (e.g. unauthorized, forbidden, not_found, invalid_body, invalid_url, precondition_failed, no_secondary_key). Clients dispatch on code, not on the message (which may be localised or contextualised in future versions).
Request-id correlation handle echoed on the request-id response header — support teams correlate without scraping logs.
Human-readable explanation of the failure.
Per-error structured detail. The shape varies by code (e.g. forbidden_action carries {"action": "..."}; precondition_failed carries {"current_version": N}).
Examplegenerated
{ "error": { "code": "example", "instance_id": "example", "message": "example", "parameters": "example" }}rail_unavailable — Stripe could not be reached (a transport failure, a first-time 5xx, a 429) or still holds the packet’s key in flight. Nothing is lost: the packet is on its way, and the same request again finishes it under the same keys.
Structured error wrapper. Every non-2xx response from any Aerandil endpoint serialises to this shape; the HTTP status carries the failure class (4xx/5xx), the body adds a stable code for programmatic dispatch plus a human-readable message.
object
object
Stable, programmatic error code (e.g. unauthorized, forbidden, not_found, invalid_body, invalid_url, precondition_failed, no_secondary_key). Clients dispatch on code, not on the message (which may be localised or contextualised in future versions).
Request-id correlation handle echoed on the request-id response header — support teams correlate without scraping logs.
Human-readable explanation of the failure.
Per-error structured detail. The shape varies by code (e.g. forbidden_action carries {"action": "..."}; precondition_failed carries {"current_version": N}).
Examplegenerated
{ "error": { "code": "example", "instance_id": "example", "message": "example", "parameters": "example" }}Headers
Section titled “Headers”Seconds to wait before retrying the same request (RFC 9110 §10.2.3).