Quickstart

One complete, runnable path from zero to a tracked refund: authenticate, create a quote, read it back, understand the decision, submit the refund, and track it to a terminal status. Every step below shows a full curl command and a full response.

🚧

This entire walkthrough targets Sandbox, not Production. Fulfillment is disabled in Sandbox, so nothing here can execute against a real, live ticket. Get your Sandbox URL and credentials from Environments & access before you start; do not substitute https://api.deal-engine.com/v3 (Production) into these commands.

Throughout, replace:

  • $SANDBOX_URL: https://api.sandbox.deal-engine.com/v3 (EU customers use https://eu.api.sandbox.deal-engine.com/v3 instead). Full endpoint matrix: Environments & access
  • $TOKEN: the access token from Step 1
  • 1282132019309: the sample ticket number used consistently below (swap in your own test ticket once you have one)

Step 1: Authenticate

Run the login mutation to get an access token. No Authorization header is needed for this call.

curl "$SANDBOX_URL" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "mutation Login($email: String!, $password: String!) { login(email: $email, password: $password) { token refreshToken expiresIn } }",
    "variables": { "email": "[email protected]", "password": "********" }
  }'

Response:

{
  "data": {
    "login": {
      "token": "<accessToken>",
      "refreshToken": "<refreshToken>",
      "expiresIn": 900
    }
  }
}

token is a JWT, valid for ~15 minutes. Use it as $TOKEN below. Full detail on lifetimes, refreshing, and error handling: Authentication.

Step 2: Create a quote

2a. Asynchronous (default): what you get back immediately

Omit the Sync header (or send Sync: false). This is the recommended default for integrations.

curl "$SANDBOX_URL" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "mutation CreateQuote($request: [quoteRequest]!) { createQuote(request: $request) }",
    "variables": { "request": [{ "ticketNum": "1282132019309", "isEmd": false }] }
  }'

Response; this is only an acknowledgment, not a quote:

{
  "data": {
    "createQuote": true
  }
}

This is the point where the current getting-started walkthrough stops, and where a reader never actually sees a quote. Don't stop here. The quote is being computed in the background; the next two steps get you the real numbers.

2b. Synchronous: the full quote in one call

For this walkthrough, use Sync: true so you can see the complete quote result immediately instead of waiting on a webhook:

curl "$SANDBOX_URL" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -H "Sync: true" \
  -d '{
    "query": "mutation CreateQuote($request: [quoteRequest]!) { createQuote(request: $request) }",
    "variables": { "request": [{ "ticketNum": "1282132019309", "isEmd": false }] }
  }'

Response (abbreviated; the full object includes refundCandidateCoupons, refundCandidateTaxes, refundability, and more):

{
  "data": {
    "createQuote": [
      {
        "decision": "VOLUNTARY",
        "refundCandidate": {
          "ticketId": 464159578,
          "status": "REVIEW",
          "refundAmount": 487.14,
          "refundCurrencyCode": "EUR",
          "refundFareAmount": 460,
          "refundTaxAmount": 87.14,
          "refundPenaltyAmount": 60,
          "isGuaranteedRefund": false
        },
        "isSafeForRefund": true
      }
    ]
  }
}

The complete response shape, field-by-field, is documented in API usage & integration §3.2.1; link there rather than guessing at a field you don't see above.

📘

Pick one mode per ticket. Don't mix async and sync for the same ticketNum; see Refund lifecycle & statuses §1.1.

Step 3: Read the quote back from the document

Whichever mode you used in Step 2, the stored quote is readable via document(ticketNum).

🚧

If you used the async default, gate on the right status, not merely on the event arriving. refund_request.status_updated can also carry in-progress values (NEW, TICKETS_FROM_PNR_INSERTED) before the quote is ready. Reading on the first event you receive is a read-too-early race on every quote. Wait for REFUND_CANDIDATE_READY (or INSERTED_AND_CREATED_REFUND_CANDIDATE_READY) before reading. Every value and its meaning: Status & error reference.

curl "$SANDBOX_URL" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "query Document($ticketNum: String!) { document(ticketNum: $ticketNum) { id ticketNum quote { id status refundableBaseFareAmount product } } }",
    "variables": { "ticketNum": "1282132019309" }
  }'

Response:

{
  "data": {
    "document": {
      "id": "464159578",
      "ticketNum": "1282132019309",
      "quote": {
        "id": "981273645",
        "status": "REVIEW",
        "refundableBaseFareAmount": 460,
        "product": "ARP"
      }
    }
  }
}

Illustrative values, consistent with the synchronous quote sample in API usage & integration; your ids and amounts will differ.

status here is a quote/candidate status rather than the decision. All status fields in this API (quote, document, candidate, and webhook payloads) share the same confirmed enum: NEW / REVIEW / READY / REFUNDED / ERROR. REFUNDED and ERROR are terminal. More on how Document, Quote, and Request relate: Document ↔ quote.

Step 4: Understand the decision before you act

The decision field from Step 2 (VOLUNTARY above) tells you whether (and how) to proceed:

DecisionAction
VOLUNTARY / INVOLUNTARY / IRREGULAR OPERATIONSProceed with processRefund (Step 5)
VOLUNTARY MILESProceed if you want the tax-only refund; miles are reinstated outside Deal Engine
REJECTEDDon't auto-retry; route to manual review
Quote ERROR_PROCESSING_REQUESTSubmit a fresh quote with backoff; contact support if persistent. Not the string ERROR; that is a candidate terminal state, and matching "ERROR" at the quote stage never fires

Full decision reference: Refund lifecycle & statuses §4. Every status vocabulary in one place: Status & error reference.

Does the candidate need approving first?

The sample above returns a candidate at "status": "REVIEW"; held for manual review. REVIEW means the quotation needs customer-side validation: for example the reservation was purged, or the fare rules are private or unreachable in the PSS. A candidate that fulfillment will pick up is READY.

🚧

Fulfillment only executes candidates in READY. A REVIEW candidate needs your validation first; resolve whatever triggered the review, then move it to READY via updateQuote (see API usage & integration §3.2.2) before calling processRefund. processRefund does not implicitly approve REVIEW candidates.

Step 5: Submit the refund

curl "$SANDBOX_URL" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "mutation ProcessRefund($input: [processFulfillmentRequest]) { processRefund(input: $input) }",
    "variables": { "input": [{ "fulfillmentRequest": { "ticketNum": "1282132019309" } }] }
  }'

Response:

{
  "data": {
    "processRefund": {
      "success": true,
      "message": "All documents processed successfully",
      "results": [
        {
          "ticketNum": "1282132019309",
          "success": true,
          "message": "Queued for processing"
        }
      ]
    }
  }
}
🚧

This response is an enqueue acknowledgment; never the refund outcome. "Queued for processing" means the ticket passed basic validation and entered the queue, nothing more. In Sandbox, fulfillment never runs, so this candidate will not reach REFUNDED. This is expected; see Environments & access. Full explanation of why the Sync header can't change this behavior for processRefund: Request parameters §The Sync header. Partial-success and all-failed response shapes: API usage & integration §3.5.

Step 6: Track to a terminal status

In Production this step ends at REFUNDED or ERROR. In Sandbox, expect it to stop at READY or REVIEW; REFUNDED is unreachable here (see Environments & access).

Option A; webhook (recommended): subscribe to refund_candidate.status_updated, which fires for both terminal outcomes. Setup, payloads, and signature verification: Webhooks guide and Webhook API reference.

Option B; polling:

curl "$SANDBOX_URL" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "query Document($ticketNum: String!) { document(ticketNum: $ticketNum) { quote { status fulfillment { fareToRefund taxesToRefund fareRulePenalty totalRefundAmount currency } } } }",
    "variables": { "ticketNum": "1282132019309" }
  }'

Poll every 30–60 seconds and stop at a terminal status. The executed amounts (fare amount to refund, tax amount to refund, penalty amount, total refund amount; all gross) are read as plain fields on the document's quote. Full guidance: Refund lifecycle & statuses §5 and Refund amounts & reconciliation.

When it doesn't work


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