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 substitutehttps://api.deal-engine.com/v3(Production) into these commands.
Throughout, replace:
$SANDBOX_URL:https://api.sandbox.deal-engine.com/v3(EU customers usehttps://eu.api.sandbox.deal-engine.com/v3instead). Full endpoint matrix: Environments & access$TOKEN: the access token from Step 11282132019309: 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 sameticketNum; 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_updatedcan 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 forREFUND_CANDIDATE_READY(orINSERTED_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:
| Decision | Action |
|---|---|
VOLUNTARY / INVOLUNTARY / IRREGULAR OPERATIONS | Proceed with processRefund (Step 5) |
VOLUNTARY MILES | Proceed if you want the tax-only refund; miles are reinstated outside Deal Engine |
REJECTED | Don't auto-retry; route to manual review |
Quote ERROR_PROCESSING_REQUEST | Submit 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 inREADY. AREVIEWcandidate needs your validation first; resolve whatever triggered the review, then move it toREADYviaupdateQuote(see API usage & integration §3.2.2) before callingprocessRefund.processRefunddoes not implicitly approveREVIEWcandidates.
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 reachREFUNDED. This is expected; see Environments & access. Full explanation of why theSyncheader can't change this behavior forprocessRefund: 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
- Auth errors (
Unauthorized, expired token): Authentication § Error Handling and Error handling §4.3. - Validation errors, rate limits (429), retries: Error handling.
- A candidate stuck below
READY, or a decision you don't understand: Refund lifecycle & statuses §4. - Still stuck: open a support ticket with your
ticketNumand the environment you were calling (Sandbox vs Production) so support can find the request without a round trip: Support, Submitting a support ticket.
Updated about 18 hours ago