Context
This weekend, I tried to delete an account on a website, but I couldn't. The website has an obscure bug that prevents the submission of the deletion form... 😕
You know a #web app is scrap when you need to look at the #JavaScript code to delete your account... 🤦♂️
— Benjamin Rancourt (@BenRancourt) February 20, 2022
Since I didn't want to contact their support (they really aren't great) and I really want to delete my account, I started looking at their source code to see if I could find a way around it. 😏
After many unsuccessful attempts (probably too many), I finally managed to delete my account after writing a Fetch request. 🤗
In this post, I want to share a little trick that you might want to use if you're playing with some APIs: the Copy as function.
Explanation
If you open Chrome DevTools (F12), navigate to the Network tab and click on the Fetch/XHR filter, you'll see all requests that use the Fetch API (you may need to refresh the page).

Network tab inside Chrome DevTools.But, did you know that by right-clicking on one of the requests, you can copy it to reproduce it? 😲

Copy of options in DevTools.Chromium browsers offer us no less than five ways to a request:
- Copy as PowerShell
- Copy as fetch
- Copy as Node fetch
- Copy as cURL (cmd)
- Copy as cURL (bash)
Let's take a look at the code they generated.
fetch
await fetch("https://www.benjaminrancourt.ca/ghost/api/canary/admin/site/", {
"headers": {
"accept": "application/json, text/javascript, */*; q=0.01",
"accept-language": "en,fr-CA;q=0.9,fr;q=0.8,la;q=0.7",
"app-pragma": "no-cache",
"cache-control": "no-cache",
"content-type": "application/json; charset=UTF-8",
"pragma": "no-cache",
"sec-ch-ua": "\" Not A;Brand\";v=\"99\", \"Chromium\";v=\"98\", \"Google Chrome\";v=\"98\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"Windows\"",
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-origin",
"x-ghost-version": "4.36",
"x-requested-with": "XMLHttpRequest"
},
"referrer": "https://www.benjaminrancourt.ca/ghost/",
"referrerPolicy": "strict-origin-when-cross-origin",
"body": null,
"method": "GET",
"mode": "cors",
"credentials": "include"
});If you take this command and paste it inside the Chrome console, you should get a response, because this Ghost API endpoint is public.
But depending on the API you want to contact, not all these headers and parameters might not be required. In this case, I can remove most of them. The final command looks like the one below.
await fetch("https://www.benjaminrancourt.ca/ghost/api/canary/admin/site/");Simpler, right? 😏
Node Fetch
await fetch("https://www.benjaminrancourt.ca/ghost/api/canary/admin/site/", {
"headers": {
"accept": "application/json, text/javascript, */*; q=0.01",
"accept-language": "en,fr-CA;q=0.9,fr;q=0.8,la;q=0.7",
"app-pragma": "no-cache",
"cache-control": "no-cache",
"content-type": "application/json; charset=UTF-8",
"pragma": "no-cache",
"sec-ch-ua": "\" Not A;Brand\";v=\"99\", \"Chromium\";v=\"98\", \"Google Chrome\";v=\"98\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"Windows\"",
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-origin",
"x-ghost-version": "4.36",
"x-requested-with": "XMLHttpRequest",
"cookie": "ghost-admin-api-session=MASKED",
"Referer": "https://www.benjaminrancourt.ca/ghost/",
"Referrer-Policy": "strict-origin-when-cross-origin"
},
"body": null,
"method": "GET"
});If you carefully compare between the fetch and Node fetch, you might notice that there is not much difference. But one of them is the presence of the cookie header in Node fetch. If this header is missing from your Node.js script, chances are you can't contact the API.
cURL (cmd)
If you're on Windows and using the default terminal, you'll probably want to use this cCURL command.
curl "https://www.benjaminrancourt.ca/ghost/api/canary/admin/site/" ^
-H "authority: www.benjaminrancourt.ca" ^
-H "pragma: no-cache" ^
-H "cache-control: no-cache" ^
-H "sec-ch-ua: ^\^" Not A;Brand^\^";v=^\^"99^\^", ^\^"Chromium^\^";v=^\^"98^\^", ^\^"Google Chrome^\^";v=^\^"98^\^"" ^
-H "dnt: 1" ^
-H "sec-ch-ua-mobile: ?0" ^
-H "app-pragma: no-cache" ^
-H "user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.82 Safari/537.36" ^
-H "content-type: application/json; charset=UTF-8" ^
-H "accept: application/json, text/javascript, */*; q=0.01" ^
-H "x-requested-with: XMLHttpRequest" ^
-H "x-ghost-version: 4.36" ^
-H "sec-ch-ua-platform: ^\^"Windows^\^"" ^
-H "sec-fetch-site: same-origin" ^
-H "sec-fetch-mode: cors" ^
-H "sec-fetch-dest: empty" ^
-H "referer: https://www.benjaminrancourt.ca/ghost/" ^
-H "accept-language: en,fr-CA;q=0.9,fr;q=0.8,la;q=0.7" ^
-H "cookie: ghost-admin-api-session=MASKED" ^
--compressedcURL (bash)
For Linux users, the separator used to continue the command is different in Bash:
curl 'https://www.benjaminrancourt.ca/ghost/api/canary/admin/site/' \
-H 'authority: www.benjaminrancourt.ca' \
-H 'pragma: no-cache' \
-H 'cache-control: no-cache' \
-H 'sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="98", "Google Chrome";v="98"' \
-H 'dnt: 1' \
-H 'sec-ch-ua-mobile: ?0' \
-H 'app-pragma: no-cache' \
-H 'user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.82 Safari/537.36' \
-H 'content-type: application/json; charset=UTF-8' \
-H 'accept: application/json, text/javascript, */*; q=0.01' \
-H 'x-requested-with: XMLHttpRequest' \
-H 'x-ghost-version: 4.36' \
-H 'sec-ch-ua-platform: "Windows"' \
-H 'sec-fetch-site: same-origin' \
-H 'sec-fetch-mode: cors' \
-H 'sec-fetch-dest: empty' \
-H 'referer: https://www.benjaminrancourt.ca/ghost/' \
-H 'accept-language: en,fr-CA;q=0.9,fr;q=0.8,la;q=0.7' \
-H 'cookie: ghost-admin-api-session=MASKED' \
--compressedYou now have four ways to play with the APIs you have access to! 😆






