# Fetch posts without Ghost Admin API from Cloudflare Worker

**URL:** <https://forum.ghost.org/t/fetch-posts-without-ghost-admin-api-from-cloudflare-worker/27756>\
**Category:** Developer help\
**Created:** [January 6, 2022, 1:59pm UTC](https://forum.ghost.org/t/fetch-posts-without-ghost-admin-api-from-cloudflare-worker/27756 "2022-01-06T13:59:43Z")\
**Posts on this page:** 6\
**Page:** 1

<div class="post-metadata">

**Author:** ![HauntedThemes](https://sea1.discourse-cdn.com/flex015/user_avatar/forum.ghost.org/hauntedthemes/32/15301_2.png) [@HauntedThemes](https://forum.ghost.org/u/HauntedThemes)\
**Post date:** [January 6, 2022, 1:59pm UTC](https://forum.ghost.org/t/fetch-posts-without-ghost-admin-api-from-cloudflare-worker/27756/1 "2022-01-06T13:59:43Z")

</div>

Hello,

I’m trying to fetch the posts from a local Ghost install using a Cloudflare Worker.

Here is what I tried and where I’m stuck.

Ghost Admin API library doesn’t seem to work since it requires Node.js modules like `fs` for example. I was able to add in webpack config this and seems to be ok:

```auto
module.exports = {
    target: "webworker",
    entry: "./index.js",
    node: {
        fs: "empty"
    }
}

```

Anyway … I tried without it to make sure that it doesn’t block anything else.

Now, I tried without the library. First, I tried to generate the Authorization token like this:

```auto
// Admin API key goes here
const key = MY_ADMIN_API_KEY;

// Split the key into ID and SECRET
const [id, secret] = key.split(':');

// Create the token (including decoding secret)
const token = jwt.sign({}, Buffer.from(secret, 'hex'), {
  keyid: id,
  algorithm: 'HS256',
  expiresIn: '5m',
  audience: `/v3/admin/`
});

return token;

```

The token seems to be generated ok.

If I try to use it like this:

```auto
const url = 'http://localhost:2368/ghost/api/v3/admin/posts/';
const headers = { Authorization: `Ghost ${token}` };
const payload = { posts: [{ title: 'Hello World' }] };
await axios.get(url, payload, { headers })
    .then(response => console.log(response))
    .catch(error => console.error(error));

```

I simply get `error` without anything else.

So I moved to Postman and pasted the generated token into Authorization → Type: API Key

Key: Ghost  
Value: Generated token at previous step  
Add to Header

The response that I get is this:

```auto
{
    "errors": [
        {
            "message": "Authorization failed",
            "context": "Unable to determine the authenticated user or integration. Check that cookies are being passed through if using session authentication.",
            "type": "NoPermissionError",
            "details": null,
            "property": null,
            "help": null,
            "code": null,
            "id": "ff6286a0-6ef6-11ec-8255-f9406afb7644"
        }
    ]
}

```

Any ideas what might be the issue here?

---

<div class="post-metadata">

**Author:** ![HauntedThemes](https://sea1.discourse-cdn.com/flex015/user_avatar/forum.ghost.org/hauntedthemes/32/15301_2.png) [@HauntedThemes](https://forum.ghost.org/u/HauntedThemes)\
**Post date:** [January 7, 2022, 9:13am UTC](https://forum.ghost.org/t/fetch-posts-without-ghost-admin-api-from-cloudflare-worker/27756/2 "2022-01-07T09:13:36Z")

</div>

Apparently the token that I’m generating is not ok.

```auto
// Admin API key goes here
const key = GHOST_ADMIN_API_KEY;

// Split the key into ID and SECRET
const [id, secret] = key.split(':');

// Create the token (including decoding secret)
const token = jwt.sign({}, Buffer.from(secret, 'hex'), {
  keyid: id,
  algorithm: 'HS256',
  expiresIn: '5m',
  audience: `/v4/admin/`
});

return token;

```

If I simply do:

```auto
curl -H "Authorization: Ghost token_generated_previously" https://hauntedthemes.ghost.io/ghost/api/v4/admin/posts/

```

I get:

```auto
{
   "errors":[
      {
         "message":"Invalid token",
         "context":null,
         "type":"BadRequestError",
         "details":null,
         "property":null,
         "help":null,
         "code":"INVALID_JWT",
         "id":"ad3d9560-6f94-11ec-b67c-9bff0097dc8f"
      }
   ]
}

```

Not sure what I’m missing here.

Seems to be the correct way to generate a token:

[https://ghost.org/docs/admin-api/#token-authentication](https://ghost.org/docs/admin-api/#token-authentication)

[https://github.com/TryGhost/SDK/blob/main/packages/admin-api/lib/token.js](https://github.com/TryGhost/SDK/blob/main/packages/admin-api/lib/token.js)

---

<div class="post-metadata">

**Author:** ![kose\_mark](https://sea1.discourse-cdn.com/flex015/user_avatar/forum.ghost.org/kose_mark/32/14237_2.png) [@kose\_mark](https://forum.ghost.org/u/kose_mark)\
**Post date:** [January 7, 2022, 11:09pm UTC](https://forum.ghost.org/t/fetch-posts-without-ghost-admin-api-from-cloudflare-worker/27756/3 "2022-01-07T23:09:01Z")

</div>

> [@HauntedThemes](#):
>
> `token`

Interesting. I’ve used Ghost Admin API from everywhere - Node.js(both admin client and JWT generation), Python, curl, Postman. Have not had a problem , unless I am mistyping something.  
Make sure:

- Your Admin Api key is correct
- You’re not sending request after token expires
- audience matches your request URL
- You’re not hitting the wrong Ghost server - most obvious but had to say it 🙂 I see localhost and Ghost Pro site names there

It would be helpful to know your Ghost version, jwt library you’re using (jsonwebtoken?) and version

---

<div class="post-metadata">

**Author:** ![HauntedThemes](https://sea1.discourse-cdn.com/flex015/user_avatar/forum.ghost.org/hauntedthemes/32/15301_2.png) [@HauntedThemes](https://forum.ghost.org/u/HauntedThemes)\
**Post date:** [January 8, 2022, 5:34pm UTC](https://forum.ghost.org/t/fetch-posts-without-ghost-admin-api-from-cloudflare-worker/27756/4 "2022-01-08T17:34:57Z")

</div>

Hi @kose_mark ,

Thanks for reaching out. So here is where I am right now.

1. Your Admin Api key is correct → Double check it, it is
2. You’re not sending request after token expires → I don’t think it’s expired since it has a 5m validity
3. Audience matches your request URL → It’s `/v4/admin/` in both.
4. … \> Double checked everything to make sure that is ok.

So here is how I continued:

I’m still generating the token like the above method that seems ok.

Then I wrote:

```auto
var data = await fetch("https://hauntedthemes.ghost.io/ghost/api/v4/admin/posts/'", {
  headers: {
    "Authorization": `Ghost ${token}`,
    "Content-Type": "application/json"
  },
  method: "GET"
}).then(res => res.json());

return new Response(JSON.stringify(data));

```

And this returns now:

```auto
{
  "errors": [
    {
      "message": "Validation error, cannot read post.",
      "context": "Validation (matches) failed for id undefined.id",
      "type": "ValidationError",
      "details": null,
      "property": null,
      "help": null,
      "code": null,
      "id": "f887c720-70a7-11ec-b67c-9bff0097dc8f"
    }
  ]
}

```

---

<div class="post-metadata">

**Author:** ![HauntedThemes](https://sea1.discourse-cdn.com/flex015/user_avatar/forum.ghost.org/hauntedthemes/32/15301_2.png) [@HauntedThemes](https://forum.ghost.org/u/HauntedThemes)\
**Post date:** [January 8, 2022, 10:55pm UTC](https://forum.ghost.org/t/fetch-posts-without-ghost-admin-api-from-cloudflare-worker/27756/5 "2022-01-08T22:55:12Z")

</div>

@kose_mark

I think I found my issue and why my requests don’t work ok.

Apparently Ghost Admin API is using `axios` library that it’s not compatible with Cloudflare Workers.

> <https://github.com/axios/axios/issues/1219>
>
> I didn't see an issue for this (opened or closed), but if this is a duplicate ju…st close it 😸 . 
> 
> Are there any plans for a \`fetch\` based adapter for axios? I thought I'd check and see if this has been discussed, and if not, if you all would be open to a feature request (or I'm happy to work on it and PR it). 
> 
> Reasons for adding a \`fetch\` adapter are basically for the same reasons you'd use fetch:
> \- Support for service workers
> \- Integration with Cache API 
> \- natively promise based, so axios can become a thinner library
> \- response streaming 
> \- probably more that I'm not even thinking of right now
> 
> Reasons you'd still want axios wrapping fetch for you (because some people feel like you don't need a lib over fetch): 
> \- fetch is low level. Using plain fetch in an app that is 99% GET/PUT/POST against a JSON API means lots of duplicate boilerplate code which axios can abstract
> \- request/response interceptors
> \- probably more that I'm not even thinking of right now :smile:

As a conclusion, for now at least, we can’t use Ghost Admin API inside a Cloudflare Worker. Instead, I’m using `fetch` to build the calls and seems to work.

I just have to figure out what are the proper values that needs to be added. For example, the above example `https://hauntedthemes.ghost.io/ghost/api/v4/admin/posts/` throws a Validation error but works if I simply put `https://hauntedthemes.ghost.io/ghost/api/v4/admin/posts/?limit=30`.

To figure this out, I went to Ghost Dashboard and simply made the request there and looked at the Headers.

---

<div class="post-metadata">

**Author:** ![kose\_mark](https://sea1.discourse-cdn.com/flex015/user_avatar/forum.ghost.org/kose_mark/32/14237_2.png) [@kose\_mark](https://forum.ghost.org/u/kose_mark)\
**Post date:** [January 9, 2022, 3:26am UTC](https://forum.ghost.org/t/fetch-posts-without-ghost-admin-api-from-cloudflare-worker/27756/6 "2022-01-09T03:26:08Z")

</div>

Yeah, I’m not familiar with Cloudflare workers except that it is a serverless product.

Looks like you figured out what’s needed, but feel free to share your experience and questions in the thread
