# 403 when attempting to post via API

**URL:** <https://forum.ghost.org/t/403-when-attempting-to-post-via-api/51624>\
**Category:** Integrations & API\
**Created:** [October 12, 2024, 7:32pm UTC](https://forum.ghost.org/t/403-when-attempting-to-post-via-api/51624 "2024-10-12T19:32:36Z")\
**Posts on this page:** 10\
**Page:** 1

<div class="post-metadata">

**Author:** ![jchadwick](https://sea1.discourse-cdn.com/flex015/user_avatar/forum.ghost.org/jchadwick/32/32598_2.png) [@jchadwick](https://forum.ghost.org/u/jchadwick)\
**Post date:** [October 12, 2024, 7:32pm UTC](https://forum.ghost.org/t/403-when-attempting-to-post-via-api/51624/1 "2024-10-12T19:32:36Z")

</div>

I’ve written a Node application to create posts (see below), but every time I run it, I get a 403. However, if I use the same exact token, URL, and payload and send the request via the VS Code Rest Client, it works great, so clearly the authentication is correct.

Same issue if using `fetch` vs `axios`. I even told ChatGPT to convert it to Python and tried that with the same exact result.

Any thoughts as to why this could happen?

```js
const GHOST_API_URL="http://localhost/ghost/api/admin"
const GHOST_API_KEY=1111111111:111111111

async function publishToGhost() {
    const [id, secret] = GHOST_API_KEY.split(':');
    const token = jwt.sign({}, Buffer.from(secret, 'hex'), {
        keyid: id,
        algorithm: 'HS256',
        expiresIn: '5m',
        audience: `/admin/`
    });

    const response = await axios.post(
        `${GHOST_API_URL}/posts`,
        { posts: [{ title: "Welcome!" }] },
        { 
             headers: { 
                Authorization: `Ghost ${token}`,
                "Content-Type": "application/json" 
             }
        }
    )

    if (response.status === 201) {
        console.log(`Published ${response.data.posts.length} posts: \r\n${response.data.posts.map(post => post.title).join('\r\n')}`);
    } else {
        throw new Error(`Failed to publish posts: ${response.status} ${response.statusText}`);
    }
}

```

---

<div class="post-metadata">

**Author:** ![Cathy\_Sarisky](https://sea1.discourse-cdn.com/flex015/user_avatar/forum.ghost.org/cathy_sarisky/32/25750_2.png) [@Cathy\_Sarisky](https://forum.ghost.org/u/Cathy_Sarisky)\
**Post date:** [October 12, 2024, 7:42pm UTC](https://forum.ghost.org/t/403-when-attempting-to-post-via-api/51624/2 "2024-10-12T19:42:32Z")

</div>

Try sticking a trailing slash on that URL…

---

<div class="post-metadata">

**Author:** ![jchadwick](https://sea1.discourse-cdn.com/flex015/user_avatar/forum.ghost.org/jchadwick/32/32598_2.png) [@jchadwick](https://forum.ghost.org/u/jchadwick)\
**Post date:** [October 12, 2024, 7:53pm UTC](https://forum.ghost.org/t/403-when-attempting-to-post-via-api/51624/3 "2024-10-12T19:53:06Z")

</div>

Do you mean

```auto
`${GHOST_API_URL}/posts/`

```

instead of

```auto
`${GHOST_API_URL}/posts`

```

That didn’t work either…

---

<div class="post-metadata">

**Author:** ![Cathy\_Sarisky](https://sea1.discourse-cdn.com/flex015/user_avatar/forum.ghost.org/cathy_sarisky/32/25750_2.png) [@Cathy\_Sarisky](https://forum.ghost.org/u/Cathy_Sarisky)\
**Post date:** [October 12, 2024, 7:58pm UTC](https://forum.ghost.org/t/403-when-attempting-to-post-via-api/51624/4 "2024-10-12T19:58:14Z")

</div>

Yeah, that is what I meant. I’ve got a cloud function that makes posts. Let me find the code and I’ll post it.

Oh, and if you haven’t found the SDK yet, it tends to remove these headaches, IF it supports what you’re trying to do…

---

<div class="post-metadata">

**Author:** ![Cathy\_Sarisky](https://sea1.discourse-cdn.com/flex015/user_avatar/forum.ghost.org/cathy_sarisky/32/25750_2.png) [@Cathy\_Sarisky](https://forum.ghost.org/u/Cathy_Sarisky)\
**Post date:** [October 12, 2024, 8:06pm UTC](https://forum.ghost.org/t/403-when-attempting-to-post-via-api/51624/5 "2024-10-12T20:06:45Z")

</div>

Here’s an API-based example - slightly messy because I cut out some logic you didn’t need.

````auto
export async function pushPostToGhost(HTML, date, dateFull) {

    const api = new GhostAdminAPI({
      url: process.env.GHOST_API_URL,
      version: "v5.0",
      key: process.env.GHOST_ADMIN_API_KEY
      });

      let title = `What's Happening - ${date}, ${year}`
            // create a new post
            let post = {
                title: title,
                html: HTML,
                status: 'draft',
                tags: ['#daily-newsletter'],
                email_only: true,
                custom_excerpt: `News and events ${date}, ${year}` 
            }
            let response = await api.posts.add(post, {source: 'html'});
}
      ```
````

---

<div class="post-metadata">

**Author:** ![jchadwick](https://sea1.discourse-cdn.com/flex015/user_avatar/forum.ghost.org/jchadwick/32/32598_2.png) [@jchadwick](https://forum.ghost.org/u/jchadwick)\
**Post date:** [October 12, 2024, 8:17pm UTC](https://forum.ghost.org/t/403-when-attempting-to-post-via-api/51624/6 "2024-10-12T20:17:28Z")

</div>

Thank you! Sorry, I also forgot to mention that I also got the same 403 error even with the JS admin API!

---

<div class="post-metadata">

**Author:** ![Cathy\_Sarisky](https://sea1.discourse-cdn.com/flex015/user_avatar/forum.ghost.org/cathy_sarisky/32/25750_2.png) [@Cathy\_Sarisky](https://forum.ghost.org/u/Cathy_Sarisky)\
**Post date:** [October 12, 2024, 8:28pm UTC](https://forum.ghost.org/t/403-when-attempting-to-post-via-api/51624/7 "2024-10-12T20:28:17Z")

</div>

Anything informative in your Ghost error logs?

---

<div class="post-metadata">

**Author:** ![jchadwick](https://sea1.discourse-cdn.com/flex015/user_avatar/forum.ghost.org/jchadwick/32/32598_2.png) [@jchadwick](https://forum.ghost.org/u/jchadwick)\
**Post date:** [October 13, 2024, 4:22am UTC](https://forum.ghost.org/t/403-when-attempting-to-post-via-api/51624/8 "2024-10-13T04:22:47Z")

</div>

Thank you that helped a lot.  
The logs were showing a 301 error and long story short the URL I was trying to call was not the one configured in the Ghost config. Thank you!

(Though I am still flabbergasted as to why it worked calling it from VS Code… never got to the root of that one.)

---

<div class="post-metadata">

**Author:** ![Cathy\_Sarisky](https://sea1.discourse-cdn.com/flex015/user_avatar/forum.ghost.org/cathy_sarisky/32/25750_2.png) [@Cathy\_Sarisky](https://forum.ghost.org/u/Cathy_Sarisky)\
**Post date:** [October 13, 2024, 10:54am UTC](https://forum.ghost.org/t/403-when-attempting-to-post-via-api/51624/9 "2024-10-13T10:54:55Z")

</div>

Ahha! Glad you found it.

I suspect your vs code extension follows redirects by default.

---

<div class="post-metadata">

**Author:** ![WizardofFuturing](https://sea1.discourse-cdn.com/flex015/user_avatar/forum.ghost.org/wizardoffuturing/32/33071_2.png) [@WizardofFuturing](https://forum.ghost.org/u/WizardofFuturing)\
**Post date:** [November 6, 2024, 7:48pm UTC](https://forum.ghost.org/t/403-when-attempting-to-post-via-api/51624/10 "2024-11-06T19:48:28Z")

</div>

Posting API. Has someone already tried to code the posts going to 20 or so publications via RSS? Syndication -ish?

That’s what I’m working on once I can get the trained GPT to send my email via this schema through Render’s endpoint.
