3. API usage & integration

3.1 Which GraphQL calls handle creating, updating, and cancelling refunds?

  • Create: createQuote(request: [quoteRequest]!, isRequote?: Boolean)
  • Update: updateQuote(request: quoteUpdateRequest!, requote?: Boolean)
  • Cancel: cancelQuote(ticketNum: String!): Boolean
📘

createQuote also accepts a fetchFromGDS parameter, but it's for internal load-testing use only; do not set it in normal integrations.

3.2 What are the required vs. optional fields?

3.2.1 CreateQuote:

mutation CreateQuote($request: [quoteRequest]!) {
    createQuote(request: $request)
}

 Minimal variables:

{
    "request": [{ "ticketNum": "1282132019309" }]
}

request accepts up to 100 tickets per call.

Optional fields in quoteRequest include:

  • pnr: is a digital record of a passengers booking: flights, details, and services.
  • gds: is global network that distributes travel inventory and enables real-time booking.
  • agent: is the user or system that books travel through a PSS.
  • issueDate: the tickets creations date.
  • refundRequestDate: date refund was requested.
  • refundRequestedAt: timestamp when the refund request was logged in the system.
  • waiver: exception granted by the airline that overrides normal rules or penalties.
  • freeText: is an open field booking or ticket where agents can add notes or special instruction.
  • isEmd: stands for individual security electronic miscellaneous document used for security services.
  • metadata: data that describes other data.
  • iataCode: is the 2-3 letter identifier asign by IATA to airlines and airports.
  • clientData: is stored information about a customer, like name, contacts, and preferences (customer information record).

Example using more variables:

{
  "request": [
    {
      "ticketNum": "0192382929393",
      "gds": "sabre",
      "iataCode": "10839203"
    }
  ]
}

Using the Sync header

When you send a quote, you can control the response behavior using the Sync header. Request examples for both modes: Request parameters. End-to-end call order: Quote workflow.

  • No Sync header / Sync = false → Immediate response, confirmation of sending, processing occurs later. createQuote is the boolean true; an acknowledgment, not a result.
{
  "data": {
    "createQuote": true
  }
}

Track completion with the refund_request.status_updated webhook, then read the quote from the document; see the Webhooks guide. Don't wait a fixed interval before reading.

  • With Sync header = true → Blocks up to 3 minutes and returns an array of document-shaped quote results (same shape as document(ticketNum)); a different JSON type from the acknowledgment above. Response times: Performance & uptime.
{
  "data": {
    "createQuote": [
      {
        "emdType": null,
        "documentType": "TICKET",
        "ticketNum": "1282132019309",
        "pnr": "ABC123",
        "ticketUsage": "OPEN",
        "parentTicketNum": null,
        "charges": { "totalFare": 460 },
        "flightType": "INTERNATIONAL",
        "issueDate": "2025-06-13",
        "pcc": "XXXX",
        "passenger": { "type": "ADT" },
        "conjunctionTicketNum": null,
        "quote": {
          "id": "...",
          "refundType": "FULL FARE REFUND",
          "status": "REVIEW",
          "refundReason": "VOLUNTARY",
          "safeBadge": true,
          "fulfillment": {
            "fareToRefund": 460,
            "taxesToRefund": 87.14,
            "fareRulePenalty": 60,
            "totalRefundAmount": 487.14,
            "currency": "EUR"
          },
          "taxInfo": []
        },
        "request": {
          "ticketNum": "1282132019309",
          "requestedBy": "..."
        },
        "segments": [],
        "fareCalculation": "...",
        "gds": "1A"
      }
    ]
  }
}

Field-level detail varies by ticket; treat each successful item as a document with a nested quote. Full field meanings: What is a document and What is a quote.

If an item is not ready within 3 minutes, that slot returns a timeout instead of a document; not a definitive quote failure:

{
  "error": "Failed due to timeout",
  "message": "Quote result timeout, call query document."
}

Call document(ticketNum) or wait for refund_request.status_updated. See Quote workflow and Request parameters.

Typing the createQuote response

createQuote is called with no selection set, so the value under data.createQuote arrives as one untyped JSON value rather than a typed GraphQL object. Deserialize it defensively:

  • Branch on the JSON type first. Async mode returns the boolean true; sync mode returns an array of document-shaped results, and any array item may be the timeout object above instead of a document. Check the JSON type and the presence of error before binding; never assume one shape.
  • Do not bind to a rigid class. Fields inside the document and its nested quote vary by tenant configuration and ticket type. Parse into a tolerant structure (a JSON tree, a map, or a class with unknown-field passthrough) and read the fields you need by name.
  • Parse amounts carefully. Monetary values are JSON numbers in major units; parse into a decimal type, not a binary float, before doing arithmetic.
  • Treat absent and null the same unless a field's documentation says otherwise, and tolerate new fields appearing without notice.

The same applies to processRefund, which is also called with no selection set (see 3.5).

3.2.2 UpdateQuote:

This mutation is used to update an existing quote with new request parameters.

mutation UpdateQuote($request: quoteUpdateRequest!, $requote: Boolean) {
    updateQuote(request: $request, requote: $requote)
}

 Minimal variables:

{
  "request": { "ticketNum": "1282132019309" }
}

quoteUpdateRequest supports many updatable fields using { from, to } envelopes (e.g., waiver, freeText, clientVisibility, fulfillment, taxes, etc.).

Updating the waiver of a quote

  • null → value or value → null: send requote: true.
{
  "request": {
    "ticketNum": "1282132019309",
    "waiver": { "from": null, "to": "Sample Waiver" }
  },
  "requote": true
}
  • value → different value: requote can be omitted or false.
{
    "request": {
        "ticketNum": "1282132019309",
        "waiver": { "from": "Old", "to": "New" }
    },
    "requote": false
}
  •  no change (both null): omit requote or false.

3.2.3 CancelQuote:

mutation CancelQuote($ticketNum: String!) {
    cancelQuote(ticketNum: $ticketNum)
}

 Variables:

{ "ticketNum": "1282132019309" }
📘

Every status value in one place. Candidate, request, quote, document, coupon and webhook vocabularies (plus the error catalog and a safe polling pattern) are consolidated in Status & error reference.

3.3 How do I paginate or filter results?

For filtering use filter inputs decorated with @filter-enabled fields. Operators include equality and generated variants like not, in, contains, _lt/_lte/_gt/gte, etc. You can also combine with AND/OR.

Example (roles where name contains "admin"):

query Roles($limit: Int, $offset: Int, $roleFilter: RoleFilter) {
    roles(limit: $limit, offset: $offset, roleFilter: $roleFilter) {
        edges {
            node {
                id
                name
                description
            }
        }
        pageInfo {
            total
            perPage
            totalPages
            hasNextPage
            hasPreviousPage
        }
    }
}

Variables:

{ "limit": 10, "offset": 0, "roleFilter": { "name_contains": "admin" } }

For pagination (limit & offset), fields annotated with @paginate accept limit and offset and return

{
  edges {
    cursor
    node
  },
  pageInfo {
    total
    perPage
    totalPages
    hasNextPage
    hasPreviousPage
  }
}

Defaults: limit 10, offset 0. Maximum: limit 100 rows per page.

Example (second page of roles):

{ "limit": 10, "offset": 10, "roleFilter": {} }

3.4 Do you support idempotent operations to prevent duplicates?

Yes, keyed on the 13-digit ticket number. Both createQuote and processRefund are idempotent per ticket: submitting a refund (or quote) for the same ticket number multiple times returns an acknowledgement rather than triggering duplicate processing. Deal Engine's internal idempotency on the ticket number prevents a second in-flight or repeated submission from creating a second quote or a second refund.

This covers the common retry case (a client resubmitting after a timeout or transport error) without any extra header or client-supplied token.

🔧

Belt and braces: reconcile per ticket after a timeout anyway. Idempotency removes the risk of a blind retry, but it doesn't remove the value of confirming state before you act on it:

  • After a timeout or transport error, poll document(ticketNum) (see 3.6) or wait for refund_candidate.status_updated to confirm what actually happened before treating the ticket as failed.
  • Keep your own submission ledger keyed by ticketNum so you always know what you last sent and can correlate it with what the API reports back.
  • A terminal candidate (REFUNDED / ERROR) never changes on resubmission.

3.5 How do I request a refund?

  • Step 1: Make sure you have a quote for the ticket in question.

  • Step 2: In GraphQL, run the processRefund mutation.

    mutation ProcessRefund($input: [processFulfillmentRequest]) {
      processRefund(input: $input)
    }

    Minimal variables:

    {
      "input": [
        {
          "fulfillmentRequest": {
            "ticketNum": "1739240193092"
          }
        }
      ]
    }

    You can submit one or multiple refund requests in a single call; up to 100 tickets per call.

    {
      "input": [
        {
          "fulfillmentRequest": {
            "ticketNum": "1739240193923"
          }
        },
    		{
          "fulfillmentRequest": {
            "ticketNum": "1739240193092"
          }
        }
      ]
    }

    Optional fields: gds, iata, and issueDate. If you don’t know them, you can omit them (they’ll be filled in from the ticket number when possible).

    {
      "input": [
        {
          "fulfillmentRequest": {
            "ticketNum": "1739240193923",
            "issueDate": null,
            "isEmd": null,
            "pnr": null,
            "refundMode": null,
            "testingMode": null
          },
          "gds": null,
          "iata": null
        }
      ]
    }

    You’ll receive a summary object with a top-level success and message, plus (in the responses documented below) a results array carrying one entry per submitted ticket. Like createQuote, processRefund is called with no selection set, so the whole object arrives as one untyped JSON value; see Typing the createQuote response in 3.2.1 for how to deserialize that safely.

    All tickets successfully enqueued for processing.

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

    Partial success.

    {
      "data": {
        "processRefund": {
          "success": false,
          "message": "Some documents processed; see results",
          "results": [
            {
              "ticketNum": "23260543607",
              "success": false,
              "message": "Invalid ticket num"
            },
            {
              "ticketNum": "0722989792111",
              "success": true,
              "message": "Queued for processing"
            }
          ]
        }
      }
    }

    No tickets were enqueued for processing.

    {
      "data": {
        "processRefund": {
          "success": false,
          "message": "No valid requests to process",
          "results": [
            {
              "ticketNum": "23260543607",
              "success": false,
              "message": "Invalid ticket num"
            },
            {
              "ticketNum": "07229897111",
              "success": false,
              "message": "Invalid ticket num"
            }
          ]
        }
      }
    }

    Reading the processRefund response

    Two shapes are documented for this mutation across these docs, and no declared type reconciles them:

    ShapeWhere it is documentedFields
    With per-ticket detailThe three examples abovesuccess, message, results[]; each result has ticketNum, success, message
    Summary onlyRequest parameters → the Sync headersuccess, message; no results

    Until that is resolved, integrate against the intersection:

    • Treat results as optional. Read success first; if results is absent, fall back to the top-level success/message rather than dereferencing an array that may not be there.
    • Branch on the booleans, never on the strings. message values; "All documents processed successfully", "Some documents processed; see results", "No valid requests to process", "Queued for processing", "Invalid ticket num"; are not declared stable and are not a versioned contract. Match on success at the level you need, and per-ticket by ticketNum. Log message for humans; don't parse it.
    • Top-level success: false does not mean nothing happened. In the partial-success example one ticket was still enqueued. Reconcile per ticketNum, not per call.
    • A per-ticket success: true means enqueued only: never that the refund executed. Track the outcome with webhooks or polling: Refund lifecycle & statuses.
  • Step 3: Your refund request is complete; the status should be visible in a few seconds.

3.6 How do I search the refund details?

Once your refund request has been created, you can verify and review its information using the following query.

This query allows you to retrieve the details of a specific refund; including its status, associated files, and request metadata.
You can also extend the query to include additional related data as needed, depending on your integration or data requirements.

Read the executed refund status and amounts as plain fields on quote; there is no separate view to select. Amounts are gross.

query Documents($filter: DocumentFilter) {
   documents(filter: $filter) {
     edges {
       node {
         ticketNum
         iataCode
         commissionPercent
         quote {
           product
           refundRoute
           status
           fulfillment {
             fareToRefund
             taxesToRefund
             fareRulePenalty
             totalRefundAmount
             currency
           }
           taxInfo {
             code
             refundAmount {
               quantity
               currencyCode
             }
           }
         }
       }
     }
   }
}
  • status: the refund status enum: NEW, REVIEW, READY, REFUNDED, ERROR. See Refund lifecycle & statuses.
  • fulfillment.fareToRefund: fare amount to refund (gross).
  • fulfillment.taxesToRefund: tax amount to refund (gross).
  • fulfillment.fareRulePenalty: penalty amount (gross).
  • fulfillment.totalRefundAmount: total refund amount (gross).
  • fulfillment.currency: the refund currency.


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