# Price a flight offer

> Price a cached flight offer to confirm the current fare. Required before booking: book_flight reads the priced offer from this cache.

Endpoint: `POST /api/v1/flights/price_flight`

Source: https://fx-port.com/docs/flights/price

Price an offer from a search to get its current, confirmed fare. This step is **required** before `book_flight`: the booking reads the priced offer, and an offer that was never priced is refused with `404 offer_not_priced`.

**Permission:** Read. **Rate group:** default (6,000 requests per minute per key).


> [!WARNING]
> Use the **supplier-level** `searchId` of the entry the offer is in (`suppliers[n].searchId`), not the top-level `requestId`. A `requestId` is answered with `404 search_not_found`.


## What it does

- **Duffel**: fetches the offer again from the supplier, so you get the live price (0.2 to 10 seconds).
- **Amadeus Algeria**: re-prices the saved offer through the supplier's pricing service (1 to 2 seconds).
- The priced offer, with your agency's commission, is saved for **12 hours** under the **same** offer id. Pricing the same offer again is allowed and returns the current price.
- `supportHold`, `supportVoid` and `voidDeadline` are evaluated again: **read them here** before you choose between `hold` and `issue`. An offer that could be held at search time may not be holdable now.

## Request body

| Field | Type | Required | Description |
|---|---|---|---|
| `search_id` | string | Yes | `suppliers[n].searchId` of the search response. |
| `offer_id` | string | Yes | `id` of the offer, `fx_offer_` and 8 hex characters. |

Both fields are ids of up to 128 letters, digits and `- _ . : = ~`. Nothing else is read. The offer must belong to that search.

### Legacy names

`searchId` and `offerId` still work and are answered with a `deprecated_field` warning.

## Request example


**Request**

```bash
curl https://api.fx-port.com/api/v1/flights/price_flight \
  -H "Authorization: Bearer fxp_test_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "search_id": "fs_dfx_a3aeaa36-74ad-424b-b88d-e6bdc9a4ba94",
  "offer_id": "fx_offer_89d6592c"
}'
```


## Response example

A Duffel offer: the supplier prices in USD and the agency settles in DZD, so `exchangeRate` is at the **top level** (the `pricing` block never holds a rate). The itinerary and the traveler pricing are the same objects as in [Search flights](https://fx-port.com/docs/flights/search.md#response-essentials) and are shortened here.

~~~json
{
  "environment": "sandbox",
  "success": true,
  "requestId": "76f8fc58",
  "offerId": "fx_offer_89d6592c",
  "offerType": "priced",
  "processingTime": 0.177,
  "supplierCurrency": "USD",
  "agencyCurrency": "DZD",
  "exchangeRate": 250.0,
  "results": {
    "totalOffers": 1,
    "supplierCurrency": "USD",
    "agencyCurrency": "DZD",
    "offers": [
      {
        "id": "fx_offer_89d6592c",
        "type": "flight_offer",
        "supplier": { "id": "duffel_1", "name": "Duffel", "originalId": "off_034q6JKduVe2mSW1fH8lge" },
        "source": "duffel",
        "bookableSeats": null,
        "supportHold": false,
        "supportVoid": false,
        "voidDeadline": null,
        "filtersApplied": {},
        "warnings": [],
        "instantTicketingRequired": false,
        "pricing": {
          "supplierCurrency": "USD",
          "agencyCurrency": "DZD",
          "basePrice": 16975.0,
          "taxesAndFees": 5366.8,
          "agencyCommission": 22.35,
          "finalPrice": 22364.15,
          "b2bPrice": 22341.8,
          "additionalServices": []
        },
        "itinerary": [{ "direction": "outbound", "duration": "PT2H24M", "stops": 0, "segments": ["..."], "conditions": { "...": "..." } }],
        "travelerPricing": [{ "travelerId": "pas_0000BC0a5iH5UE0bmBqWkl", "travelerType": "ADULT", "price": { "...": "..." }, "fareDetailsBySegment": ["..."] }],
        "conditions": { "refundBeforeDeparture": { "allowed": true }, "changeBeforeDeparture": { "allowed": true } },
        "fareRules": { "refund": { "allowed": true }, "exchange": { "allowed": true }, "supplierCurrency": "USD", "agencyCurrency": "DZD" },
        "validatingAirline": [{ "code": "BA", "name": "British Airways" }],
        "additionalServices": {
          "baggage": [
            {
              "id": "ase_0000BC0aGG1cCZZ8LCieTb",
              "type": "checked",
              "quantity": 1,
              "maxQuantity": 1,
              "maxWeightKg": 23,
              "maxWeightUnit": "kg",
              "priceAmount": 20.0,
              "convertedPriceAmount": 5000.0,
              "priceCurrency": "USD",
              "applicablePassengers": ["pas_0000BC0a5iH5UE0bmBqWkl"],
              "applicableSegments": ["seg_0000BC0a5iVGdVLqUATqqr"],
              "bookableByItinerary": false,
              "metadata": { "name": "CHECKED", "description": "Additional checked" }
            }
          ]
        }
      }
    ]
  }
}
~~~

The offer is the search offer **plus** `source` (`duffel` or `GDS`) and the priced `additionalServices` (extra baggage the traveler can buy: a list of priced services under `baggage`), and **minus** `lastTicketingDate` and `lastTicketingDateTime`.

## Response fields

| Field | Type | Meaning |
|---|---|---|
| `success` | boolean | `true` when the offer was priced. |
| `requestId` | string | The id of this call, for support. |
| `offerId` | string | Echoes your `offer_id`. |
| `offerType` | string | `priced`. |
| `supplierCurrency`, `agencyCurrency` | string | The two currencies; see [Flight pricing](https://fx-port.com/docs/flights/pricing.md). |
| `exchangeRate` | number | Only when the two currencies differ: the rate used. |
| `processingTime` | number | Seconds spent calling the supplier. |
| `results.offers[0]` | object | The priced offer. Its `id` is the `offer_id` for `book_flight`. |
| `results.offers[0].supportHold`, `.supportVoid`, `.voidDeadline` | | Re-evaluated at pricing time: decide hold or issue from **these**. |
| `results.offers[0].pricing` | object | The confirmed price. Compare `finalPrice` with the search: it can differ. |
| `results.offers[0].travelerPricing` | array | The confirmed price of each traveler, as in [search](https://fx-port.com/docs/flights/search.md#travelerpricingn-fields). |
| `results.offers[0].conditions`, `.fareRules` | object | Refund and change eligibility of this exact fare (indicative). |

Next to the keys above the response carries the snake_case twins `request_id`, `offer_id`, `exchange_rate`, `processing_time` and, in the offer, `offer_id`, `support_hold`, `support_void`, `void_deadline`, `pricing.base_price`, `pricing.taxes_and_fees`, `pricing.agency_commission`, `pricing.final_price`, `pricing.b2b_price`, `supplier.supplier_id`, `supplier.supplier_name`, `supplier.supplier_offer_id`.

**What the response does not say**: when the offer expires, and the `searchId` (you must keep it). Duffel offers expire after 15 to 60 minutes: in our sandbox an offer was priced without a problem 26 minutes after the search and was gone at 38 minutes. Book soon after you price.

## Errors

| Status | Code | When | What to do |
|---|---|---|---|
| `400` | `bad_request` | `search_id` or `offer_id` is missing: `Missing required parameters: search_id and offer_id are required`. | Send both. |
| `400` | `invalid_identifier` | An id is not a valid token (letters, digits and `- _ . : = ~`, up to 128 characters). | Send the id exactly as you received it. |
| `400` | `invalid_json`, `invalid_body` | The body is not a JSON object. | Send a JSON object. |
| `404` | `search_not_found` | The `search_id` is unknown, older than 24 hours, a `requestId`, or belongs to another agency (the answer is the same). | Search again. |
| `404` | `offer_not_found` | The offer is not in that search. | Check that the offer comes from the supplier entry of this `searchId`. |
| `410` | `offer_expired` | The supplier says the offer is gone (`supplier_offer_id` is in the body). | Search again. |
| `410` | `offer_incomplete`, `supplier_not_found`, `technology_type_unknown` | The saved offer is unusable. | Search again. |
| `503` | `supplier_temporarily_unavailable` | The supplier is being skipped after repeated failures (`SUPPLIER_CIRCUIT_OPEN`). | Wait a little, then retry. |
| `503` | `supplier_api_error` | The supplier refused or failed. | Retry once; otherwise search again. |
| `503` | `supplier_busy` | FX-Port's capacity towards the supplier is full for a moment. The supplier was not called and nothing was priced. The answer carries `Retry-After` (seconds). | Wait for `Retry-After`, then send the same request again: it is safe. |

See [Errors](https://fx-port.com/docs/errors.md) for the body of an error and [Search flights](https://fx-port.com/docs/flights/search.md) for the 24-hour life of a `searchId`.

## Next

[Book the flight](https://fx-port.com/docs/flights/book.md) with the same `search_id` and the `id` of the priced offer. For what each price field means, see [Flight pricing](https://fx-port.com/docs/flights/pricing.md).
