Javascript + MailChimp API subscribe - javascript

When making this request:
// Subscribe a new account holder to a MailChimp list
function subscribeSomeoneToMailChimpList()
{
var options =
{
"apikey": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"id": "xxxxxx",
"email":
{
"email": "me#example.com"
},
"send_welcome": false
};
var mcSubscribeRequest = UrlFetchApp.fetch("https://us4.api.mailchimp.com/2.0/lists/subscribe.json", options);
var mcListObject = Utilities.jsonParse(mcSubscribeRequest.getContentText());
}
This response is returned:
Request failed for https://us4.api.mailchimp.com/2.0/lists/subscribe.json returned code 500. Truncated server response: {"status":"error","code":-100,"name":"ValidationError","error":"You must specify a apikey value"} (use muteHttpExceptions option to examine full response) (line 120, file "v2")
Line 120 is the line on which UrlFetchApp.fetch is called.
The API key is valid (I have tested with simpler API calls that don't include associative arrays). When I append the API key directly to the base URL and remove it from the options, I get an error saying that the list ID is invalid. When I then append the list ID directly to the base URL and remove it from options, I get an error saying that the email address must be in associative array form.
My question is: Using the above format, how does one send requests that contain associative arrays?
The relevant API documentation can be found here.

After further research & tinkering, I was able to solve this:
https://<dc>.api.mailchimp.com/2.0/lists/subscribe.json?apikey=<my_api_key>&id=<my_list_id>&email[email]=test#test.com&merge_vars[FNAME]=John&merge_vars[LNAME]=Doe&double_optin=false&send_welcome=false
Where <dc> should be replaced with the portion after the dash in your API Key. e.g. "us1", "us2", "uk1", etc.

Doing this in javascript exposes your API key to the world. If someone has your key, he/she can make changes to or gain access to your account.

I think I figured out what is going on after reading through the UrlFetchApp.fetch Docs.
https://developers.google.com/apps-script/reference/url-fetch/url-fetch-app?csw=1#fetch(String)
It looks like you should be using some of the extra params to do the request such as payload and method. Your options variable should look like this.
var payload = {
"apikey": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"id": "xxxxxx",
"email": {
"email": "me#example.com"
},
"send_welcome": false
};
payload = Utilities.jsonStringify(payload); // the payload needs to be sent as a string, so we need this
var options = {
method: "post",
contentType: "application/json", // contentType property was mistyped as ContentType - case matters
payload: payload
};
var result = UrlFetchApp.fetch("https://<dc>.api.mailchimp.com/2.0/lists/subscribe.json", options);
Where <dc> should be replaced with the portion after the dash in your API Key. e.g. "us1", "us2", "uk1", etc.
The issue is that your options variable is suppose to be used as JSON and not as a GET url parameter. Also mailchimp specifies that it is better to use POST instead of GET. So over all you should make sure to set you method to "post" and make sure your payload is valid JSON.

Related

FastAPI returns "Error 422: Unprocessable entity" when I send multipart form data with JavaScript Fetch API

I have some issue with using Fetch API JavaScript method when sending some simple formData like so:
function register() {
var formData = new FormData();
var textInputName = document.getElementById('textInputName');
var sexButtonActive = document.querySelector('#buttonsMW > .btn.active');
var imagesInput = document.getElementById('imagesInput');
formData.append('name', textInputName.value);
if (sexButtonActive != null){
formData.append('sex', sexButtonActive.html())
} else {
formData.append('sex', "");
}
formData.append('images', imagesInput.files[0]);
fetch('/user/register', {
method: 'POST',
data: formData,
})
.then(response => response.json());
}
document.querySelector("form").addEventListener("submit", register);
And on the server side (FastAPI):
#app.post("/user/register", status_code=201)
def register_user(name: str = Form(...), sex: str = Form(...), images: List[UploadFile] = Form(...)):
try:
print(name)
print(sex)
print(images)
return "OK"
except Exception as err:
print(err)
print(traceback.format_exc())
return "Error"
After clicking on the submit button I get Error 422: Unprocessable entity. So, if I'm trying to add header Content-Type: multipart/form-data, it also doesn't help cause I get another Error 400: Bad Request. I want to understand what I am doing wrong, and how to process formData without such errors?
The 422 response body will contain an error message about which field(s) is missing or doesn’t match the expected format. Since you haven't provided that (please do so), my guess is that the error is triggered due to how you defined the images parameter in your endpoint. Since images is expected to be a List of File(s), you should instead define it using the File type instead of Form. For example:
images: List[UploadFile] = File(...)
^^^^
When using UploadFile, you don't have to use File() in the default value of the parameter. Hence, the below should also work:
images: List[UploadFile]
Additionally, in the frontend, make sure to use the body (not data) parameter in the fetch() function to pass the FormData object (see example in MDN Web Docs). For instance:
fetch('/user/register', {
method: 'POST',
body: formData,
})
.then(res => {...
Please have a look at this answer, as well as this answer, which provide working examples on how to upload multiple files and form data to a FastAPI backend, using Fetch API in the frontend.
As for manually specifying the Content-Type when sending multipart/form-data, you don't have to (and shouldn't) do that, but rather let the browser set the Content-Type—please take a look at this answer for more details.
So, I found that I has error in this part of code:
formData.append('images', imagesInput.files[0]);
Right way to upload multiple files is:
for (const image of imagesInput.files) {
formData.append('images', image);
}
Also, we should use File in FastAPI method arguments images: List[UploadFile] = File(...) (instead of Form) and change data to body in JS method. It's not an error, cause after method called, we get right type of data, for example:
Name: Bob
Sex: Man
Images: [<starlette.datastructures.UploadFile object at 0x7fe07abf04f0>]

Autodesk 2 Legged Authentication in javascript, two different errors

I get two different errors depending on if I GET or POST, but my autodesk rep assures me that using postman (not javascript) the resource does exist and he can get an authentication token.
If I do:
var url = "https://developer.api.autodesk.com/authentication/v1/authenticate";
var options = {
"method": "GET",
"headers":{"Content-Type": "application/x-www-form-urlencoded",},
"body": {
"client_id" : "Z---F",
"client_secret" : "m---8",
"grant_type": "client_credentials",
"scope": "data:read"
}
}
;
console.log(url);
console.log("Options:\n"+JSON.stringify(options));
var res = UrlFetchApp.fetch(url, options).getContentText();
Logger.log(res);
}
I get
Exception: Request failed for https://developer.api.autodesk.com returned code 404. Truncated server >response: { "developerMessage":"The requested resource does not exist.", "moreInfo": >https://forge.autodesk.com/en/docs/oauth/v2/developers_guide/error_hand... (use muteHttpExceptions >option to examine full response)
if I do
var url = "https://developer.api.autodesk.com/authentication/v1/authenticate";
var options = {
"method": "POST",
"headers":{"Content-Type": "application/x-www-form-urlencoded",},
"body": {
"client_id" : "Z---F",
"client_secret" : "m---8",
"grant_type": "client_credentials",
"scope": "data:read"
}
}
;
console.log(url);
console.log("Options:\n"+JSON.stringify(options));
var res = UrlFetchApp.fetch(url, options).getContentText();
Logger.log(res);
}
I get
Exception: Request failed for https://developer.api.autodesk.com returned code 400. Truncated server >response: {"developerMessage":"The required parameter(s) client_id,client_secret,grant_type not present >in the request","errorCode":"AUTH-008","more info":"h... (use muteHttpExceptions option to examine full >response)
Any thoughts why it would say a resource doesn't exist that does exist? There is no personal info on here so the fact that he is doing it versus me doing it shouldn't matter. All the posts I could find on this issue were dealing with the curl version of this call rather than a javascript with options bundle
The URL is definitely valid (here's the API reference for this endpoint), so I would recommend debugging the actual code that's making the request, for example:
debug the UrlFetchApp class (where is it coming from btw?) and its fetch method, making sure that it's no modifying the url you're passing in
if the code is running in a browser, try looking at the DevTools Network tab and see if the request actually goes out to https://developer.api.autodesk.com/authentication/v1/authenticate
if the code is running in a browser, try using the built-in Fetch API instead
Also, I'm not sure if this is something handled automatically by the UrlFetchApp but specifying the content type of your request as application/x-www-form-urlencoded and then passing in a JSON object as the body doesn't seem right. As you can see in the documentation the request body should look more like this:
client_id=<your client id>&
client_secret=<your client secret>&
grant_type=client_credentials&
scope=data:read

API with Google Script Apps

I'm having trouble using Google Apps to interact with a management software called Kissflow.
function fun2() {
var id = "yyy";
var apisecretkey = "xxx";
var url ='https://'+id+'.kissflow.com/api/1/verify';
var options = {
method: 'post',
headers : {"Authorization" : " Basic " + Utilities.base64Encode(id + ":" + apisecretkey)},
payload: {
"grant_type": "client_credentials",
"scope": "basic+user"
},
muteHttpExceptions: true
};
var response = JSON.parse(UrlFetchApp.fetch(url, options).getContentText());
}
I would like to run this simple example of the API documentation, the goal is for me to be able to send data to the software through my interactions in a spreadsheet, for example. If you can help me in this I will be very grateful, I am new with API's :)
The following error appears: SyntaxError: unexpected token <in JSON at position 0 (line 30, file "Code") I don't know if I'm using this function correctly.
Kissflow API Documentation
JSON.parse(UrlFetchApp.fetch(url, options).getContentText())
Remove JSON.parse() from above line and output the response on your console using Logger.log() or to your browser log using console.log() and see the result. If there are errors it'll show more user friendly error message.
While I'm not familiar with the API, I suspect the issue is related to this line:
var response = JSON.parse(UrlFetchApp.fetch(url, options).getContentText());
You're fetching the response (which is probably JSON), then getting the content text of that response, then trying to parse that text as if it were JSON.
So (solution 1) if you set your response variable equal to
var response = UrlFetchApp.fetch(url, options).getContentText();
and try printing that variable to the console, you may find you have something you can work with.
Alternatively (solution 2), you might try:
var response = JSON.parse(UrlFetchApp.fetch(url, options));
if you'd rather have a JavaScript object to work with instead of a string (and assuming the .fetch method you're calling does indeed provide a JSON-formatted response.)

ionic native HTTP call with content-type text/plan?

I am trying to make a HTTP call and my Content-Type is text/plan but I am working with ionic-native/http which only take object and array,here is what I have tried
Error: advanced-http: "data" argument supports only following data types: Array, Object
TestIR(item){
let headers = { 'Content-Type': 'text/plain'};
let sender ='sendir,1:1,0,';
let code = item.keys[10].code;
let body = sender+code;
let url= "http://10.75.0.120/v2/CI00e0148a";
this.httpNative.setDataSerializer( "json" );
this.httpNative.post(url,body,headers).then(val=>
{console.log(val)}).
catch(e=>{console.log(e)})
}
Assumption: The HTTP plugin being used is cordova-plugin-advanced-http, linked from the Ionic Docs.
Setting the Data Serializer to json tells the plugin to expect a javascript object, but the body you are sending is a string
Solution 1
If the API you are sending data to can handle it, set the Data Serializer to "utf8".
this.httpNative.setDataSerializer( "utf8" );
This tells the plugin to expect text.
Solution 2
Change the body to an object before calling POST.
body = JSON.parse(json);
The string you pass to JSON.parse() must be valid json.
Edit : Removed references to a bug that is now fixed
If you are using the cordova-plugin-advanced-http you could also do the following if you only want to set the content type for one request.
const options: any = {
serializer: 'utf8',
method: 'post',
data: 'my string value'
};
this.http.sendRequest('https://mysite', options);
If your response type is json I recommend to parse it by yourself by using JSON.parse.
I was receiving the same error and I tried the #Glen Davies solution, but the error kept and was intermittently.
Sometimes it used to work, sometimes not.
Error: advanced-http: "data" argument supports only following data types: Array, Object
I'm using Ionic 3 application with cordova http plugin in the http-interceptor and after some hours, I've discovered the problem was that even with the serializer being set in the app.component.ts, it was not working.
The solution was moving the:
this.http.setSSLCertMode('pinned');
this.http.setDataSerializer('utf8');
To the constructor inside the HttpInterceptor.
After this, the error was gone.

Flickr API throws up "Invalid API Key (Key has invalid format)" when POSTing data with oauth signing

I can't seem to find this answered anywhere on SO or even Google - I have an oauth-signed call to the Flickr upload API, and following the docs I've signed the POST operation the usual oauth way (but without the photo data). For testing purposes I've only passed along a title and the photo data, which means I end up a var flickrURI that contains the following url for POSTing to:
https://api.flickr.com/services/upload/
? format=json
& oauth_consumer_key=...
& oauth_nonce=2e57b73fec6630a30588e22383cc3b25
& oauth_signature_method=HMAC-SHA1
& oauth_timestamp=1411933792346
& oauth_token=...
& title=test
& oauth_signature=O7JPn1m06vl5Rl95Z2in32YWp7Q%3D
(split over multiple lines for legibility in this question, the actual URL has no whitespacing around the ? and & for obvious reasons).
The oauth signing itself is quite correct, and code used for accessing the not-upload-API all over the place with correct behaviour, so it seems pretty much impossible for that to get the signing wrong, other than perhaps signing with "not enough data" or perhaps signing with "too much data".
The auth signing first forms the auth query string, in this case:
oauth_consumer_key=...
&oauth_nonce=60028905f65cf9d7649b3bce98f718f8
&oauth_signature_method=HMAC-SHA1
&oauth_timestamp=1411939726691
&oauth_token=...
&title=test
which is then used to form the verb + address + encoded base string:
POST&https%3A%2F%2Fapi.flickr.com%2Fservices%2Fupload%2F&oauth_consumer_key%3D...%26oauth_nonce%3D60028905f65cf9d7649b3bce98f718f8%26oauth_signature_method%3DHMAC
-SHA1%26oauth_timestamp%3D1411939726691%26oauth_token%3D...%26title%3Dtest
This is then HMAC-SHA1 digested using the Flickr and oauth secrets:
function sign = (data, key, secret) {
var hmacKey = key + "&" + (secret ? secret : ''),
hmac = crypto.createHmac("SHA1", hmacKey);
hmac.update(data);
var digest = hmac.digest("base64");
return encodeURIComponent(digest);
}
And for GET requests, this works perfectly fine. For POST requests things seem to be difference, despite the docs not explain which part is supposedly different, so I the tried to use the Nodejs request package to perform the POST action in what seemed a normal way, using:
uploadOptions = {
oauth_consumer_key = auth.api_key,
oauth_nonce = auth.oauth_nonce,
oauth_timestamp = auth.oauth_timestamp,
oauth_token = auth.access_token,
oauth_signature_method = "HMAC-SHA1",
title: title,
photo: <binary data buffer>
};
flickrURL = formSignedURL(auth);
request.post({
url: flickrURI,
headers: {
"Authorization": 'oauth_consumer_key="...",oauth_token="...",oauth_signature_method="HMAC-SHA1",oauth_signature="...",oauth_timestamp="...",oauth_nonce="...",oauth_version="1.0"'
},
multipart: [{
'content-type': 'application/json',
body: JSON.stringify(signOptions)
}]
},function(error, response, body) {
console.log("error");
console.log(error);
console.log("body");
console.log(body);
}
);
which yields a body that contains:
<?xml version="1.0" encoding="utf-8" ?>
<rsp stat="fail">
<err code="100" msg="Invalid API Key (Key has invalid format)" />
</rsp>
As the oauth signing doesn't really give me a choice in which API key to provide (there is only one) and the signing works just fine for the not-upload API, I can't figure out what this error message is trying to tell me. The key is definitely the right format because that's the format Flickr gives you, and it's the correct value, because it works just fine outside of uploading. I also made sure to get the oauth token and secret for that key with "delete" permission (widest possible permissions) so the included access token and access token secret should pass the "does this token for this key have permissions to write" test.
What obvious thing am I missing here that's preventing the upload to go through?
It looks like you're using the https://up.flickr.com/services/upload/ endpoint, which uses the old authentication scheme.
For OAuth, it should be https://api.flickr.com/services/upload/. Make sure the endpoint is included in signing process.
I don't think it's documented anywhere, but I remember having same issue a while back.
It turns out adding the data as request.post multipart information isn't good enough, and will make the Flickr API throw an "Invalid API Key (Key has invalid format)" error instead of saying what's actually wrong. The following request call, with exactly the same data, works:
var uploadOptions = ...
var flickrURL = ...;
var req = request.post(flickrURL, function(error, response, body) {
callback(error, body);
});
var form = req.form();
uploadOptions.photo = fs.createReadStream(...);
Object.keys(photoOptions).forEach(function(prop) {
form.append(prop, photoOptions[prop]);
});
Despite not making all that much sense call wise (why would the POST not already be done by the time we call form = req.form()?) this is request's "proper" way to send the POST payload over the wire, and makes the Flickr API process the photo upload just fine.

Categories