Microsoft graph Unable to read JSON request payload - php

I'm trying to create subscriptions with microsoft graph in php, however I am unable to see what is going wrong at this point.
The code is breaking at the following:
protected $http_subscribe = "https://graph.microsoft.com/v1.0/subscriptions";
public function getSubscriptions()
{
if(empty($this->token)) {
dd('no token supplied'); //some debugging
}
$date = $this->endTimeDate->format('Y-m-d'); //This is Carbon date
$time = $this->endTimeDate->format('H:i:s.u');
$response = $this->client->request('POST', $this->http_subscribe, [
'headers' => [
'Authorization' => 'Bearer ' . $this->token,
'Content-Type' => "application/json"
], 'form_params' => [
"changeType" => "created,updated",
"notificationUrl" => "https://website.test/notify",
"resource" => "me/mailFolders('Inbox')/messages",
"expirationDateTime" => $date.'T'.$time,
"clientState" => "secretClientValue"
]
]);
dd($response);
}
The full error I'm getting is:
"""
{\r\n
"error": {\r\n
"code": "BadRequest",\r\n
"message": "Unable to read JSON request payload. Please ensure Content-Type header is set and payload is of valid JSON format.",\r\n
"innerError": {\r\n
"request-id": "063d9947-80fd-461d-a70e-q0bd8eee9d56",\r\n
"date": "2018-08-07T08:20:54"\r\n
}\r\n
}\r\n
}
"""
Now I get what the error says, my json would be invalid, however this array is in correct json format, I'm using Guzzle as my Client.

You should use json option. Try this:
$response = $this->client->request('POST', $this->http_subscribe, [
'headers' => [
'Authorization' => 'Bearer ' . $this->token,
],
'json' => [
"changeType" => "created,updated",
"notificationUrl" => "https://website.test/notify",
"resource" => "me/mailFolders('Inbox')/messages",
"expirationDateTime" => $date.'T'.$time,
"clientState" => "secretClientValue",
],
]);

Related

Unable to POST request using guzzle with the Amadeus API

Description
I am trying to integrate Amadeus Self-Service API within the Laravel Environment. I am successfully able to get content by GET request, but I am not able to get content by the POST request. I have set the exceptions to display the errors thrown by the guzzle in specific.
Here is the api reference, which has the data and the endpoint which I want to post to.
https://developers.amadeus.com/self-service/category/air/api-doc/flight-offers-search/api-reference
How to reproduce
This is the method which I call from my Client.php and pass the data through by calling the POST method.
public function __construct() {
throw_if(static::$instance, 'There should be only one instance of this class');
static::$instance = $this;
$this->client = new Client([
'base_uri' => 'https://test.api.amadeus.com/',
]);
}
public function get($uri, array $options = []) {
$this->authenticate();
return $this->client->request('GET', $uri, [
$options,
'headers' => [
'Authorization' => 'Bearer '.$this->access_token,
],
]);
}
public function post($uri, array $options = []) {
$this->authenticate();
return $this->client->request('POST', $uri, [
$options,
'headers' => [
'Authorization' => 'Bearer '.$this->access_token,
],
]);
}
After calling the POST method, I pass the 'X-HTTP-Method-Override' as 'GET', and pass the data as body.
$requests_response = $client->post('v2/shopping/flight-offers', [
'headers' => [
'X-HTTP-Method-Override' => 'GET',
],
'body' => [
[
"currencyCode" => "USD",
"originDestinations" => [
[
"id" => "1",
"originLocationCode" => "RIO",
"destinationLocationCode" => "MAD",
"departureDateTimeRange" => [
"date" => "2022-11-01",
"time" => "10:00:00",
],
],
[
"id" => "2",
"originLocationCode" => "MAD",
"destinationLocationCode" => "RIO",
"departureDateTimeRange" => [
"date" => "2022-11-05",
"time" => "17:00:00",
],
],
],
"travelers" => [
["id" => "1", "travelerType" => "ADULT"],
["id" => "2", "travelerType" => "CHILD"],
],
"sources" => ["GDS"],
"searchCriteria" => [
"maxFlightOffers" => 2,
"flightFilters" => [
"cabinRestrictions" => [
[
"cabin" => "BUSINESS",
"coverage" => "MOST_SEGMENTS",
"originDestinationIds" => ["1"],
],
],
"carrierRestrictions" => [
"excludedCarrierCodes" => ["AA", "TP", "AZ"],
],
],
],
],
],
]);
Additional context
Here is the error, which I caught in the log.
local.ERROR: Guzzle error {"response":{"GuzzleHttp\\Psr7\\Stream":"
{
\"errors\": [
{
\"code\": 38189,
\"title\": \"Internal error\",
\"detail\": \"An internal error occurred, please contact your administrator\",
\"status\": 500
}
]
}
"}}
local.ERROR: Server error: POST https://test.api.amadeus.com/v2/shopping/flight-offers resulted in a 500 Internal Server Error response:
{
"errors": [
"code": 38189,
(truncated...)
"exception":"[object] (GuzzleHttp\\Exception\\ServerException(code: 500): Server error: POST https://test.api.amadeus.com/v2/shopping/flight-offers resulted in a 500 Internal Server Error response:
"errors": [
"code": 38189,
(truncated...)
at C:\\xampp\\htdocs\\Application\\vendor\\guzzlehttp\\guzzle\\src\\Exception\\RequestException.php:113)
Please spare some time to have a look, help is really appreciated.
Do the POST calls actually work using a HTTP client such as Postman or Insomnia ?
I am noticing is that you are passing an array of $options and are nesting it inside the Guzzle options. The resulting call will look something like this:
$this->client->request('POST', $uri, [
['headers' => '...', 'body' => ['...']],
'headers' => ['...']
]);
That won't work, you are going to need to unpack them this way:
public function post($uri, array $options = []) {
$this->authenticate();
return $this->client->request('POST', $uri, [
...$options,
'headers' => [
'Authorization' => 'Bearer '.$this->access_token,
],
]);
}
Notice the dots ... to unpack the options array. Also notice that you are setting the headers key twice (once in your post method definition and once in the options parameter), so only one will actually be used (by the way why exactly are you using the X-HTTP-Method-Override header ?).
Another solution if you want to pass all header and body in the POST function parameters is this:
public function post($uri, array $options = []) {
$this->authenticate();
return $this->client->request('POST', $uri, [
'json' => $options['json'], // I would suggest 'json' because it looks like the API is looking for a JSON body, if that doesn't work go with 'body'
'headers' => [
'Authorization' => 'Bearer '.$this->access_token,
...$options['headers']
],
]);
}
Another thing you might try if this doesn't do it is using the Guzzle json option instead of body for the POST request.
when you are exploring any Amadeus Self Service API, I recommend to review the portal, because it will help you with one idea about how to make the http calls.
In your case:
https://developers.amadeus.com/self-service/category/air/api-doc/flight-offers-search/api-reference
Another help could be to review the coding examples:
https://github.com/amadeus4dev/amadeus-code-examples/blob/master/flight_offers_search/v2/post/curl/
https://github.com/amadeus4dev/amadeus-code-examples/tree/master/flight_offers_search/v2/get/curl
Maybe it's a little late but this example work for me:
$options = [
'headers' => [
'Authorization' => sprintf('Bearer %s', $this->getApiToken()),
'content-type' => 'application/vnd.amadeus+json',
'X-HTTP-Method-Override' => 'GET',
],
'body' => '{
"currencyCode": "XPF",
"originDestinations": [
{
"id": 1,
"originLocationCode": "PPT",
"originRadius": null,
"alternativeOriginsCodes": [],
"destinationLocationCode": "CDG",
"alternativeDestinationsCodes": [],
"departureDateTimeRange": {
"date": "2022-12-22",
"dateWindow": "I2D"
},
"includedConnectionPoints": [],
"excludedConnectionPoints": []
}
],
"travelers": [
{
"id": "1",
"travelerType": "ADULT",
"associatedAdultId": null
}
],
"sources": [
"GDS"
]
}'
];
try {
...
$response = $this->httpClient->post(self::AMADEUS_API_URL_FLIGHT_OFFER, $options);
$body = $response->getBody();
...
Note: don't forget the content-type, it's not very obvious at first sight in the documentation but without it doesnt work with Guzzle for me (but with insomnia no problem)
consts of the class:
private const AMADEUS_API_CLIENT_GRANT_TYPE = 'client_credentials';
private const AMADEUS_API_URL_AUTH = '/v1/security/oauth2/token';
private const AMADEUS_API_URL_FLIGHT_OFFER = '/v2/shopping/flight-offers';
Authentication:
/**
*
*/
public function authenticate()
{
if (!is_null($this->getApiToken())) {
return $this->getApiToken();
}
$options = [
'form_params' => [
'client_id' => $this->apiId, //setted in the parent construct
'client_secret' => $this->apiKey, //setted in the parent construct
'grant_type' => self::AMADEUS_API_CLIENT_GRANT_TYPE,
]
];
try {
$response = $this->httpClient->post(self::AMADEUS_API_URL_AUTH, $options);
} catch (ClientExceptionInterface $exception) {
...
}
if ($response->getStatusCode() != Response::HTTP_OK) {
throw new ApiException($errorMessage, [$response->getReasonPhrase()], $response->getStatusCode());
}
$body = $response->getBody();
//custom serializer, AmadeusAuthenticationResponse is a mapping based on Amadeus authentication response
$authenticationResponse = $this->serializer->convertSerializationToData($body->getContents(), AmadeusAuthenticationResponse::class);
$this->setApiToken($authenticationResponse->getAccessToken());
return $this->getApiToken();
}';

Convert cURL to Guzzle Post sending json data

I have this cURL command that I need to convert to PHP using Guzzle 7. I've have been researching this for a few (well, more than a few) days and getting nowhere fast. The cURL command uses the Simpli.fi api to create an organization under the parent org.
curl -i -X POST -H "X-App-Key: xxxx-xx-xx-xx-xxxxxx" -H "X-User-Key: xxxx-xx-xx-xx-xxxxxx" \
-H "Content-Type: application/json" \
-d '{
"organization": {
"name": "My New Organization",
"parent_id": "5786",
"custom_id": "<Put your organization identifier here or omit this optional field>"
}
}' \
"https://app.simpli.fi/api/organizations"
I was able to convert it using this website but it doesn't use Guzzle: https://onlinedevtools.in/curl
Here's what it gave me:
include('vendor/rmccue/requests/library/Requests.php');
Requests::register_autoloader();
$headers = array(
'X-App-Key' => 'xxxx-xx-xx-xx-xxxxxx',
'X-User-Key' => 'xxxx-xx-xx-xx-xxxxxx',
'Content-Type' => 'application/json'
);
$data = '{\n "organization": {\n "name": "My New Organization",\n "parent_id": "5786",\n "custom_id": "<Put your organization identifier here or omit this optional field>"\n }\n }';
$response = Requests::post('https://app.simpli.fi/api/organizations', $headers, $data);
Here's what I've tried so far aside from the converted code above:
public static function createOrganization()
{
self::init();
$client = new Client(['debug' => true]);
$request = $client->request('POST',
self::$baseUrl.'/organizations', [
'multipart' => [
[
'name' => 'data',
'contents' => "{'organization':{'name':'Pete's Pet Pagoda','parent_id':'1052385'}}",
],
],
'headers' => [
'x-app-key' => self::$appKey,
'x-user-key' => self::$userKey,
'Content-Type' => 'application/json',
]
]);
dd($response = $request->getStatusCode());
}
I'm getting quite a few different errors however this is the latest:
curl_setopt_array(): cannot represent a stream of type Output as a STDIO FILE*
Can anyone tell me what I'm doing wrong? Is there something missing?
UPDATE: After further research into this issue and chatting with a developer on the Laracast Slack channel, I've come to learn that this is a bug with the ['debug' => true] option when running on a Windows system and is described on this GITHUB page: https://github.com/guzzle/guzzle/issues/1413
I'm running on a Windows 10 Pro system. I corrected it on my end by changing the debug option to use fopen() like this:
'debug' => fopen('php://stderr', 'w'),
I use PHPStorm. It suggested using the 'wb' to make it binary safe. After changing it, the post requests worked fine.
You need to use body, not multipart. You can also use json.
$request = $client->request('POST',
self::$baseUrl.'/organizations', [
'headers' => [
'x-app-key' => self::$appKey,
'x-user-key' => self::$userKey,
'Content-Type' => 'application/json',
],
'body' => [
'{"organization":
[
{
"name":"Pete`s Pet Pagoda",
"parent_id":"1052385"
}
]
}',
],
]);
method 2
You can pass array to the json request option and it will convert it to json when sending the guzzle request. No need to use header as application/json as it applies internally.
$client->request('POST',
self::$baseUrl.'/organizations', [
'headers' => [
'x-app-key' => self::$appKey,
'x-user-key' => self::$userKey,
'Content-Type' => 'application/json',
],
'json' => [
"organization" => [
[
"name" => "Pete`s Pet Pagoda"
"parent_id" => "1052385"
]
]
],
]);
I hope this will help you. For further debugging use Middleware::tap(find more help here middleware+json+request)
try{
$client = new Client();
$response = $client->request('POST', self::$baseUrl.'/organizations',
[
'headers' => [
'x-app-key' => self::$appKey,
'x-user-key' => self::$userKey,
'Content-Type' => 'application/json',
],
'json' => [
'organization' =>
[
"name" => "some_name_value",
"parent_id" => "id_here",
"custom_id" => "custom id here"
]
]
]);
$_response = json_decode($response->getBody()->getContents(), true);
}
catch(BadResponseException $e){
$response = $e->getResponse();
$errorArray = json_decode($response->getBody()->getContents(), true);
//echo "<pre>";
//print_r($errorArray);
//return some message from errorarray;
}

Guzzle post json data causing 400 bad request

I am trying to use guzzle to do a post request to an open API. I am also sending the post data as follows. For some reason i am getting a 400 bad request
$req = $client->request('POST',$this->base_url.'nutrients?app_id='.$this->app_id.'&app_key='.$this->api_key,[
'headers' => [
'Content-Type' => 'application/json'
],
\GuzzleHttp\RequestOptions::JSON => [
"yield" => $portions,
"ingredients" => [
"quantity" => $portions,
"measureURI" => "\"$f_uri\"",
"foodURI" => "\"$m_uri\""
]
]
]);
$response = $client->send($req);
$decoded = json_decode($response->getBody());
dd($decoded);
The api documentation shows the method in curl as follows:
curl call:
curl -d #food.json -H "Content-Type: application/json" "https://api.edamam.com/api/food-database/nutrients?app_id=${YOUR_APP_ID}&app_key=${YOUR_APP_KEY}"
food.json:
{
"yield": 1,
"ingredients": [
{
"quantity": 1,
"measureURI": "http://www.edamam.com/ontologies/edamam.owl#Measure_unit",
"foodURI": "http://www.edamam.com/ontologies/edamam.owl#Food_11529"
}
]
}
My links for measureURI and foodURI are generated correctly.
You don't need to do ->send() after the ->request(), it already returns an instance of ResponseInterface.
So just
$response = $client->request('POST', $this->base_url.'nutrients?app_id='.$this->app_id.'&app_key='.$this->api_key, [
// ...
]
]);
$decoded = json_decode($response->getBody()->getContents());

400 Bad Request When Adding Member to MailChimp List

I am sending a POST request to the following resource and getting a 400. I understand what the error means, but still am unsure why I'm getting it when a GET request to the same resource works.
/lists/{list_id}/members
Here is a exerpt of the code:
$client = new \GuzzleHttp\Client();
$response = $client->request('POST', // <-- Drop in a GET here and it works, other than it's not the behavior I need.
env('MAILCHIMP_API_URL') . 'lists/' . env('MAILCHIMP_LIST_KEY') . '/members',
[
'auth' => ['app', env('MAILCHIMP_API_KEY')],
'query' => [
'email_address' => 'donnie#test.com',
'email_type' => 'html',
'status' => 'subscribed',
]
]);
dd($response->getStatusCode());
Response
Client error: `POST https://XXXX.api.mailchimp.com/3.0/lists/XXXX/members?email_address=donnie%40test.com&email_type=html&status=subscribed`
resulted in a `400 Bad Request`
response: {
"type": "http://developer.mailchimp.com/documentation/mailchimp/guides/error-glossary/",
"title": "Invalid Resource",
"status": 400,
"detail": "The resource submitted could not be validated. For field-specific details, see the 'errors' array.",
"instance": "f32e7076-b970-4f5c-82c6-eec5875e83b4",
"errors": [{
"field": "",
"message": "Schema describes object, NULL found instead"
}]
}
You are sending a POST request with query parameters. You need to send JSON encoded body!
$client = new \GuzzleHttp\Client();
$response = $client->request('POST', // <-- Drop in a GET here and it works, other than it's not the behavior I need.
env('MAILCHIMP_API_URL') . 'lists/' . env('MAILCHIMP_LIST_KEY') . '/members',
[
'auth' => ['app', env('MAILCHIMP_API_KEY')],
'json' => [
'email_address' => 'donnie#test.com',
'email_type' => 'html',
'status' => 'subscribed',
]
]);
dd($response->getStatusCode());

Can you include raw JSON in Guzzle POST Body?

This should be soo simple but I have spent hours searching for the answer and am truly stuck. I am building a basic Laravel application and am using Guzzle to replace the CURL request I am making at the moment. All the CURL functions utilise raw JSON variables in the body.
I am trying to create a working Guzzle client but the server is respsonding with 'invalid request' and I am just wondering if something fishy is going on with the JSON I am posting. I am starting to wonder if you can not use raw JSON in the Guzzle POST request body? I know the headers are working as I am receiving a valid response from the server and I know the JSON is valid as it is currently working in a CURL request. So I am stuck :-(
Any help would be sooo greatly appreciated.
$headers = array(
'NETOAPI_KEY' => env('NETO_API_KEY'),
'Content-Type' => 'application/json',
'Accept' => 'application/json',
'NETOAPI_ACTION' => 'GetOrder'
);
// JSON Data for API post
$GetOrder = '{
"Filter": {
"OrderID": "N10139",
"OutputSelector": [
"OrderStatus"
]
}
}';
$client = new client();
$res = $client->post(env('NETO_API_URL'), [ 'headers' => $headers ], [ 'body' => $GetOrder ]);
return $res->getBody();
You can send a regular array as JSON via the 'json' request option; this will also automatically set the right headers:
$headers = [
'NETOAPI_KEY' => env('NETO_API_KEY'),
'Accept' => 'application/json',
'NETOAPI_ACTION' => 'GetOrder'
];
$GetOrder = [
'Filter' => [
'OrderID' => 'N10139',
'OutputSelector' => ['OrderStatus'],
],
];
$client = new client();
$res = $client->post(env('NETO_API_URL'), [
'headers' => $headers,
'json' => $GetOrder,
]);
Note that Guzzle applies json_encode() without any options behind the scenes; if you need any customisation, you're advised to do some of the work yourself
$res = $client->post(env('NETO_API_URL'), [
'headers' => $headers + ['Content-Type' => 'application/json'],
'body' => json_encode($getOrders, ...),
]);
Guzzle 7 Here
The below worked for me with raw json input
$data = array(
'customer' => '89090',
'username' => 'app',
'password' => 'pwd'
);
$url = "http://someendpoint/API/Login";
$client = new \GuzzleHttp\Client();
$response = $client->post($url, [
'headers' => ['Content-Type' => 'application/json', 'Accept' => 'application/json'],
'body' => json_encode($data)
]);
print_r(json_decode($response->getBody(), true));
For some reasons until I used the json_decode on the response, the output wasn't formatted.
You probably need to set the body mime type. This can be done easily using the setBody() method.
$request = $client->post(env('NETO_API_URL'), ['headers' => $headers]);
$request->setBody($GetOrder, 'application/json');

Categories