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

  1. Quote: createQuote reads the ticket from the airline system (PSS), applies refundability rules, and produces a refund candidate with amounts and a decision.
  2. Acceptance: processRefund submits the candidate. A successful response means accepted and queued, not done.
  3. Execution: the fulfillment engine performs the refund in the PSS and writes a terminal status: REFUNDED or ERROR.
📘

"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
StepCallNotes
1createQuote(request: [quoteRequest]!)Send Sync: false or omit the header. Batch several ticketNum in one call. Minimum field is ticketNum
2refund_request.status_updated webhookFires when the quote stage progresses. Use it as the trigger for step 3 instead of a fixed delay
3document(ticketNum)quoteReads the stored quote and amounts; see Document ↔ quote
4processRefund(input: [processFulfillmentRequest])Acceptance only, never the outcome
5refund_candidate.status_updated webhookCarries 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 | ERROR

Blocks 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:

StatusMeaningTerminal?Set by
NEWReceived. The ticket is in Deal Engine and the quotation has not produced a validated outcome yetNoEngine
REVIEWThe quotation output needs the customer's validation before it can be processedNoEngine or client
READYDeal Engine is confident the refund meets the functional rules previously aligned with you in your FSD (Functional Specification Document)NoEngine or client
REFUNDEDExecuted successfully in the airline systemYesEngine only
ERRORExecution failed after acceptanceYesEngine only

2.1 Why a candidate lands in REVIEW

REVIEW 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

FromToActorTrigger
(none)NEWEngineCandidate created from a quote
NEWREVIEWEngine or clientThe quotation needs your validation
NEWREADYEngine or clientQuotation satisfies the FSD rules
REVIEWREADYClientYou validated the quotation; set it with updateQuote (2.3)
READYREVIEWClientKill switch; see 2.4
READYREFUNDEDEngine onlyprocessRefund accepted, then fulfillment succeeded in the PSS
READYERROREngine onlyprocessRefund accepted, then fulfillment failed
REFUNDED / ERROR(nothing)N/ATerminal
  • Fulfillment only executes candidates in READY. A candidate in NEW or REVIEW is never refunded.
  • REFUNDED and ERROR are 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.
  • ERROR carries 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

After 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 READYREVIEW is the kill switch

Moving 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
IOOpen for use (unused)
OKOKConfirmed, unused
ALAAirport control
CKCChecked in
BDLBoarded / lifted
BFFlown / used
EEExchanged / reissued
RFRRefunded
PRPPrinted
PEXPrint exchange
IOOIrregular operations
NAVUNot available
CLO / ZZClosed
QQRevoked by the airline
SSSuspended
VVVoid
YYRefund 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:

ValueMeaning
VOLUNTARYPassenger-initiated, under fare rules
INVOLUNTARYAirline-caused (cancellation, schedule change); typically penalty-free
IRREGULAR OPERATIONSOperational disruption
VOLUNTARY MILESMiles-paid ticket; only monetary taxes quoted (see Refund amounts)
REJECTEDRefused 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

DecisionMeaningAction
VOLUNTARY / INVOLUNTARY / IRREGULAR OPERATIONSRefundableProceed with processRefund or your approval flow
VOLUNTARY MILESTaxes-only refundableProceed if tax refund desired; miles reinstated outside Deal Engine
REJECTEDInsufficient or missing PSS information; detected via notes + 0 refund amount, or via the REJECTED quote status. Retrying returns the same resultDon't auto-retry. Route to manual review/support with the note text
Quote ERROR_PROCESSING_REQUESTQuote 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 referenceSubmit a fresh quote with backoff; contact support if persistent
Quote REJECTEDDocument cannot be quoted due to missing information from the PSS; reason in the request's error fieldVerify the ticket number

5. Tracking a refund to completion

5.1 Webhooks (recommended)

  • refund_request.status_updated: quote-stage progress
  • refund_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.


Did this page help you?
All rights reserved © 2025 deal-engine.com.