Search code examples
javajsonspring-bootapiamadeus

amadeus api : offer pricing call produces 400 - type needed


I'm calling the offer price API and receiving a 400 with a "type needed" message.

org.springframework.web.util.NestedServletException: Request processing failed; nested exception is com.amadeus.exceptions.ClientException: [400] type needed

The call via postman API succeeds (https://test.api.amadeus.com/v1/shopping/flight-offers/pricing), so I take the body used in that, and send it via java API call where I receive the error.

This is on test env.

https://test.api.amadeus.com/v1/shopping/flight-offers/pricing

The body sent (which is taken from the working postman example):

{
  "type": "flight-offer",
  "id": "1",
  "source": "GDS",
  "instantTicketingRequired": false,
  "nonHomogeneous": false,
  "oneWay": false,
  "lastTicketingDate": "2022-11-01",
  "numberOfBookableSeats": 9,
  "itineraries": [
    {
      "duration": "PT1H20M",
      "segments": [
        {
          "departure": {
            "iataCode": "LHR",
            "terminal": "4",
            "at": "2022-11-01T06:25:00"
          },
          "arrival": {
            "iataCode": "CDG",
            "terminal": "2E",
            "at": "2022-11-01T08:45:00"
          },
          "carrierCode": "AF",
          "number": "1381",
          "aircraft": {
            "code": "320"
          },
          "operating": {
            "carrierCode": "AF"
          },
          "duration": "PT1H20M",
          "id": "1",
          "numberOfStops": 0,
          "blacklistedInEU": false
        }
      ]
    },
    {
      "duration": "PT1H20M",
      "segments": [
        {
          "departure": {
            "iataCode": "CDG",
            "terminal": "2E",
            "at": "2022-11-05T18:00:00"
          },
          "arrival": {
            "iataCode": "LHR",
            "terminal": "4",
            "at": "2022-11-05T18:20:00"
          },
          "carrierCode": "AF",
          "number": "1180",
          "aircraft": {
            "code": "319"
          },
          "operating": {
            "carrierCode": "AF"
          },
          "duration": "PT1H20M",
          "id": "6",
          "numberOfStops": 0,
          "blacklistedInEU": false
        }
      ]
    }
  ],
  "price": {
    "currency": "EUR",
    "total": "255.30",
    "base": "48.00",
    "fees": [
      {
        "amount": "0.00",
        "type": "SUPPLIER"
      },
      {
        "amount": "0.00",
        "type": "TICKETING"
      }
    ],
    "grandTotal": "255.30",
    "additionalServices": [
      {
        "amount": "50.00",
        "type": "CHECKED_BAGS"
      }
    ]
  },
  "pricingOptions": {
    "fareType": [
      "PUBLISHED"
    ],
    "includedCheckedBagsOnly": false
  },
  "validatingAirlineCodes": [
    "AF"
  ],
  "travelerPricings": [
    {
      "travelerId": "1",
      "fareOption": "STANDARD",
      "travelerType": "ADULT",
      "price": {
        "currency": "EUR",
        "total": "127.65",
        "base": "24.00"
      },
      "fareDetailsBySegment": [
        {
          "segmentId": "1",
          "cabin": "ECONOMY",
          "fareBasis": "GS50OALG",
          "brandedFare": "LIGHT2",
          "class": "G",
          "includedCheckedBags": {
            "quantity": 0
          }
        },
        {
          "segmentId": "6",
          "cabin": "ECONOMY",
          "fareBasis": "GS50OALG",
          "brandedFare": "LIGHT2",
          "class": "G",
          "includedCheckedBags": {
            "quantity": 0
          }
        }
      ]
    },
    {
      "travelerId": "2",
      "fareOption": "STANDARD",
      "travelerType": "ADULT",
      "price": {
        "currency": "EUR",
        "total": "127.65",
        "base": "24.00"
      },
      "fareDetailsBySegment": [
        {
          "segmentId": "1",
          "cabin": "ECONOMY",
          "fareBasis": "GS50OALG",
          "brandedFare": "LIGHT2",
          "class": "G",
          "includedCheckedBags": {
            "quantity": 0
          }
        },
        {
          "segmentId": "6",
          "cabin": "ECONOMY",
          "fareBasis": "GS50OALG",
          "brandedFare": "LIGHT2",
          "class": "G",
          "includedCheckedBags": {
            "quantity": 0
          }
        }
      ]
    }
  ]
}

Java API call I make couldn't be simpler:

        <!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.9.0</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/com.amadeus/amadeus-java -->
        <dependency>
            <groupId>com.amadeus</groupId>
            <artifactId>amadeus-java</artifactId>
            <version>6.2.0</version>
        </dependency>
    @PostMapping("/confirm")
    public ResponseEntity<FlightPrice> confirm(@RequestBody FlightOfferSearch search) throws ResponseException {
        return ResponseEntity.ok(amadeusConnector.confirm(search));
    }
    public FlightPrice confirm(FlightOfferSearch offer) throws ResponseException {
        return amadeus.shopping.flightOffersSearch.pricing.post(offer);
    }

Solution

  • This is an issue caused by Amadeus Java SDK, I just created a PR for this issue which has been merged, thus I believe a new patched version will be released soon.

    Additionally, if you wonder what the problem exact is, you can take a look at the following:

    As you said in the problem, when you use string body to call this API, it works. But when you use the offer object to call, it fails.

    In that case, we can try to compare the differences between offer and the string you used before.

    If you enable Amadeus Java SDK debug mode, then reproduce this issue again, you will see the log information which describes this error very clearly

    Jul 22, 2022 11:06:56 AM com.amadeus.HTTPClient log
    INFO: Response(statusCode=400, parsed=true, result={"errors":[{"code":32171,"title":"MANDATORY DATA MISSING","detail":"type needed","source":{"pointer":"/data/flightOffers[0]/price/additionalServices[0]/type"},"status":400}]}`,...)
    

    The information above means the offer object we passed is missing the type data.

    Thus, we need to add the type property into the resource.FlightOfferSearch.Price.AdditionalService resource model class.