Refund lifecycle & statuses
Every status a refund can take, what coupon statuses mean, how decisions are made, and how to track a refund to its final outcome; so you never have to discover a state by testing.
1. The lifecycle at a glance
- Quote:
createQuotereads the ticket from the airline system (PSS), applies refundability rules, and produces a refund candidate with amounts and a decision. - Acceptance:
processRefundsubmits the candidate. A successful response means accepted and queued, not done. - Execution: the fulfillment engine performs the refund in the PSS and writes a terminal status:
REFUNDEDorERROR.
"Queued for processing" is an acknowledgment, not an outcome. The final result arrives later; track it with webhooks or polling (section 5).
1.1 Quote workflow
Two ways to get a quote. Pick one per integration; don't mix them for the same ticket.
Asynchronous (recommended)
1. createQuote → { "createQuote": true } (acknowledgment only)
2. webhook → refund_request.status_updated
3. document(ticketNum) → quote { status, refundAmount, ... }
4. processRefund → "Queued for processing"
5. webhook → refund_candidate.status_updated → REFUNDED | ERROR| Step | Call | Notes |
|---|---|---|
| 1 | createQuote(request: [quoteRequest]!) | Send Sync: false or omit the header. Batch several ticketNum in one call. Minimum field is ticketNum |
| 2 | refund_request.status_updated webhook | Fires when the quote stage progresses. Use it as the trigger for step 3 instead of a fixed delay |
| 3 | document(ticketNum) → quote | Reads the stored quote and amounts; see Document ↔ quote |
| 4 | processRefund(input: [processFulfillmentRequest]) | Acceptance only, never the outcome |
| 5 | refund_candidate.status_updated webhook | Carries both terminal outcomes |
Response times: Performance & uptime.
Synchronous
1. createQuote (Sync: true) → document-shaped quote result (or timeout item)
2. processRefund → "Queued for processing"
3. webhook / polling → REFUNDED | ERRORBlocks up to 3 minutes per GraphQL createQuote call. On success, each item is a document (ticket fields, nested quote with status/amounts/fulfillment/taxInfo, segments, request, etc.); the same shape you would read later via document(ticketNum). Header details: Request parameters.
If an item times out (error: "Failed due to timeout", message tells you to call document), do not treat it as a failed quote. The quote may still complete in the background; read it with document(ticketNum) or the refund_request.status_updated webhook.
Suited to interactive single-ticket flows (an agent waiting on a number); not to batch volume. Prefer async + webhooks for high volume.
Changing a quote
updateQuote(request, requote) revises an existing quote; set requote: true when the change is material (adding or removing a waiver). cancelQuote(ticketNum) drops it. Payloads: API usage & integration.
2. Refund candidate statuses
There is one status vocabulary across the whole product. The same five values describe a refund candidate, a quote, a document, a request, and the status carried in webhook payloads; always uppercase:
| Status | Meaning | Terminal? | Set by |
|---|---|---|---|
NEW | Received. The ticket is in Deal Engine and the quotation has not produced a validated outcome yet | No | Engine |
REVIEW | The quotation output needs the customer's validation before it can be processed | No | Engine or client |
READY | Deal Engine is confident the refund meets the functional rules previously aligned with you in your FSD (Functional Specification Document) | No | Engine or client |
REFUNDED | Executed successfully in the airline system | Yes | Engine only |
ERROR | Execution failed after acceptance | Yes | Engine only |
2.1 Why a candidate lands in REVIEW
REVIEWREVIEW means Deal Engine does not have all the ingredients it needs to guarantee the quotation, so a human on your side has to confirm it. Typical causes:
- The reservation was purged, so the full quotation cannot be determined from the PSS any more.
- The fare rules are private and unreachable from the PSS, so refundability cannot be proven automatically.
READY is the opposite statement: every rule agreed in your FSD was checked and passed.
2.2 Transitions
| From | To | Actor | Trigger |
|---|---|---|---|
| (none) | NEW | Engine | Candidate created from a quote |
NEW | REVIEW | Engine or client | The quotation needs your validation |
NEW | READY | Engine or client | Quotation satisfies the FSD rules |
REVIEW | READY | Client | You validated the quotation; set it with updateQuote (2.3) |
READY | REVIEW | Client | Kill switch; see 2.4 |
READY | REFUNDED | Engine only | processRefund accepted, then fulfillment succeeded in the PSS |
READY | ERROR | Engine only | processRefund accepted, then fulfillment failed |
REFUNDED / ERROR | (nothing) | N/A | Terminal |
- Fulfillment only executes candidates in
READY. A candidate inNEWorREVIEWis never refunded. REFUNDEDandERRORare written only by the fulfillment engine. A client can never set a terminal status.- Terminal statuses never transition again. Retrying the same ticket doesn't change them.
ERRORcarries the failure detail in its error history; contact support with the ticket number if it isn't actionable on your side.
2.3 Validating a REVIEW candidate
REVIEW candidateAfter you have checked the quotation, move it to READY with updateQuote. Only then will fulfillment pick it up.
{
"request": {
"ticketNum": "1282132019309",
"refundCandidateStatus": { "from": "REVIEW", "to": "READY" }
}
}2.4 READY → REVIEW is the kill switch
READY → REVIEW is the kill switchMoving a candidate back to REVIEW stops it from being refunded: fulfillment re-verifies that the candidate is still READY immediately before executing. Use it to hold a refund you have already approved but no longer want executed.
{
"request": {
"ticketNum": "1282132019309",
"refundCandidateStatus": { "from": "READY", "to": "REVIEW" }
}
}3. Coupon statuses
Each flight segment has a coupon with a status in the airline system; refundability is evaluated per coupon.
3.1 Coupon statuses come from your PSS defaults, then your FSD
Deal Engine ships a default coupon-status mapping per PSS (one for Sabre, one for Amadeus) and that default can be customized per customer. In particular, which coupon states count as "open" (still refundable) versus "flown" (consumed) is a per-customer decision.
The mapping that applies to you is aligned during the PoC and recorded in your FSD (Functional Specification Document). That document (not this page) is the authoritative list of the codes your account receives and how each one is classified.
The reference below is the shared vocabulary the defaults are built from. Deal Engine normalizes Amadeus web-service codes to standard one-letter ("cryptic") codes, so a status is generally seen either as the raw PSS code or as the normalized short code:
| Raw (PSS) | Normalized (short) | Meaning |
|---|---|---|
I | O | Open for use (unused) |
OK | OK | Confirmed, unused |
AL | A | Airport control |
CK | C | Checked in |
BD | L | Boarded / lifted |
B | F | Flown / used |
E | E | Exchanged / reissued |
RF | R | Refunded |
PR | P | Printed |
PE | X | Print exchange |
IO | O | Irregular operations |
NAV | U | Not available |
CLO / Z | Z | Closed |
Q | Q | Revoked by the airline |
S | S | Suspended |
V | V | Void |
Y | Y | Refund taxes/fees only |
Sample responses in API usage & integration also show whole-word forms such as "OPEN" on coupons, segments and taxes (alongside isOpen: true). Confirm against your FSD and one live response for your account which form your fields carry, and parse defensively for both; the codes above and the whole-word form are not interchangeable strings.
Unrecognized codes pass through unchanged. If you receive a code you can't interpret, treat the coupon as not refundable and contact support.
3.2 Which statuses are refundable?
Refundability checks each coupon against a configured non-refundable status list. The default shipped list is:
CLO, RF, 1, B, C, E, F, G, L, N, P, Q, R, T, U, V, X, Y, Z
Everything else (open coupons (O, I, OK, A, AL)) is refundable. E (exchanged) routes through reissue-chain handling; U/NAV trigger deeper fare-rule evaluation.
This default carries entries in both forms (raw PSS codes such as CLO and RF alongside normalized letters) because it is the union of the per-PSS defaults. Your effective list is the one recorded in your FSD, resolved against the PSS you actually quote from; read it there rather than reproducing this default in code.
Fully configurable. These lists (like nearly every rule on this page) are configured per customer, market, and rule set. The values above are defaults; the mapping agreed with Deal Engine and recorded in your FSD is the source of truth.
3.3 UAT note
Fulfillment is not available in sandbox, so refunds never reach REFUNDED there and coupon statuses don't change. See Testing & environments.
4. Refund decisions
4.1 Decision values
Not a fixed enum; configured per customer and flow. Standard values:
| Value | Meaning |
|---|---|
VOLUNTARY | Passenger-initiated, under fare rules |
INVOLUNTARY | Airline-caused (cancellation, schedule change); typically penalty-free |
IRREGULAR OPERATIONS | Operational disruption |
VOLUNTARY MILES | Miles-paid ticket; only monetary taxes quoted (see Refund amounts) |
REJECTED | Refused by a configured rejection rule (4.2) |
Your configuration may add variants (e.g. VOLUNTARY_BEFORE_DEPARTURE) or informational reasons (No open coupons found). Deal Engine provides your active list at onboarding.
4.2 When is a refund REJECTED?
Documents that cannot be quoted because information is missing from the PSS are also reported with the REJECTED quotation status; the reason is delivered in the request's error field. See Status & error reference.
4.3 Decision-to-action matrix
| Decision | Meaning | Action |
|---|---|---|
VOLUNTARY / INVOLUNTARY / IRREGULAR OPERATIONS | Refundable | Proceed with processRefund or your approval flow |
VOLUNTARY MILES | Taxes-only refundable | Proceed if tax refund desired; miles reinstated outside Deal Engine |
REJECTED | Insufficient or missing PSS information; detected via notes + 0 refund amount, or via the REJECTED quote status. Retrying returns the same result | Don't auto-retry. Route to manual review/support with the note text |
Quote ERROR_PROCESSING_REQUEST | Quote failed (system/PSS). This is the exact value on refund_request.status_updated, not the string ERROR. Matching on "ERROR" here never fires; ERROR is a candidate terminal state. See Status & error reference | Submit a fresh quote with backoff; contact support if persistent |
Quote REJECTED | Document cannot be quoted due to missing information from the PSS; reason in the request's error field | Verify the ticket number |
5. Tracking a refund to completion
5.1 Webhooks (recommended)
refund_request.status_updated: quote-stage progressrefund_candidate.status_updated: candidate changes, including both terminal outcomes
Configured per client and environment; see the Webhooks guide for payloads and setup.
5.2 Polling
Poll the candidate status every 30–60 seconds (execution can take up to ~2 minutes; faster polling adds nothing). Stop at REFUNDED/ERROR (those are the only terminal values. Reconcile on the executed amounts (fare, taxes, penalty and total to refund) all gross), not on the quote estimate; see Refund amounts & reconciliation.
5.3 Failure after acceptance
Failed execution ends in ERROR (+ webhook if configured). Transient PSS failures are retried internally (2 retries on network errors) before ERROR; business refusals are never retried.
A candidate that was safe to submit can still fail at execution (PSS outage, document changed since quoting). Treat REFUNDED (not the acceptance response) as confirmation.
Updated 27 days ago