# 404 Error on Image upload

**URL:** <https://forum.ghost.org/t/404-error-on-image-upload/38519>\
**Category:** Integrations & API\
**Created:** [May 18, 2023, 4:55pm UTC](https://forum.ghost.org/t/404-error-on-image-upload/38519 "2023-05-18T16:55:02Z")\
**Posts on this page:** 7\
**Page:** 1

<div class="post-metadata">

**Author:** ![Curtis\_Duggan](https://sea1.discourse-cdn.com/flex015/user_avatar/forum.ghost.org/curtis_duggan/32/23736_2.png) [@Curtis\_Duggan](https://forum.ghost.org/u/Curtis_Duggan)\
**Post date:** [May 18, 2023, 4:55pm UTC](https://forum.ghost.org/t/404-error-on-image-upload/38519/1 "2023-05-18T16:55:02Z")

</div>

Uploading posts via API is working just fine, but I am new to uploading images.

Any idea what I’m doing wrong? (404 error)

```auto
// Import the necessary modules
import axios from 'axios';
import fs from 'fs';
import FormData from 'form-data';
import dotenv from 'dotenv';
import GhostAdminAPI from '@tryghost/admin-api';

// Initialize dotenv
dotenv.config();

// Define Ghost Admin API key and URL
const GHOST_ADMIN_API_KEY = process.env.GHOST_ADMIN_API_KEY;
const GHOST_ADMIN_URL = process.env.GHOST_ADMIN_URL;

// Ensure all necessary environment variables are set
if (!GHOST_ADMIN_API_KEY || !GHOST_ADMIN_URL) {
    console.error('ERROR: Missing necessary environment variables. Please set GHOST_ADMIN_API_KEY and GHOST_ADMIN_URL.');
    process.exit(1);
}

// Function to upload the image
const uploadImageToGhost = async (imagePath) => {
    try {
        // Check if the file exists
        if (!fs.existsSync(imagePath)) {
            console.error('ERROR: The specified image file does not exist:', imagePath);
            return;
        }

        // Create a new instance of FormData
        const formData = new FormData();

        // Add the image file to the form data
        formData.append('file', fs.createReadStream(imagePath));

        // Axios configuration for the POST request
        const config = {
            headers: {
                'Authorization': `Ghost ${GHOST_ADMIN_API_KEY}`,
                ...formData.getHeaders() // Spread operator to merge form data headers
            }
        };

        // Send POST request to Ghost API
        const response = await axios.post(`${GHOST_ADMIN_URL}/images/upload/`, formData, config);

        // Log the URL of the uploaded image
        console.log('Image uploaded successfully. Image URL:', response.data.url);
    } catch (error) {
        console.error('An error occurred while uploading the image:', error);
    }
}

// Call the function
uploadImageToGhost("./inputData/georgia.jpg");

```

---

<div class="post-metadata">

**Author:** ![jannis](https://sea1.discourse-cdn.com/flex015/user_avatar/forum.ghost.org/jannis/32/39718_2.png) [@jannis](https://forum.ghost.org/u/jannis)\
**Post date:** [May 18, 2023, 5:03pm UTC](https://forum.ghost.org/t/404-error-on-image-upload/38519/2 "2023-05-18T17:03:30Z")

</div>

A 404 error usually means that the resource you’re trying to reach can’t be found.

My best guess, based on the code, would be that `GHOST_ADMIN_URL` isn’t resolving to the correct path.

Are you sure the endpoint you’re calling is actually `https://example.com/ghost/api/admin/images/upload/`?

---

<div class="post-metadata">

**Author:** ![Curtis\_Duggan](https://sea1.discourse-cdn.com/flex015/user_avatar/forum.ghost.org/curtis_duggan/32/23736_2.png) [@Curtis\_Duggan](https://forum.ghost.org/u/Curtis_Duggan)\
**Post date:** [May 18, 2023, 5:26pm UTC](https://forum.ghost.org/t/404-error-on-image-upload/38519/3 "2023-05-18T17:26:56Z")

</div>

I have console logged and confirmed

[https://myurl.ghost.io/ghost/api/admin/images/upload/](https://myurl.ghost.io/ghost/api/admin/images/upload/)

is the URL being passed in, still debugging…

---

<div class="post-metadata">

**Author:** ![Curtis\_Duggan](https://sea1.discourse-cdn.com/flex015/user_avatar/forum.ghost.org/curtis_duggan/32/23736_2.png) [@Curtis\_Duggan](https://forum.ghost.org/u/Curtis_Duggan)\
**Post date:** [May 18, 2023, 6:04pm UTC](https://forum.ghost.org/t/404-error-on-image-upload/38519/4 "2023-05-18T18:04:12Z")

</div>

It seems there is a JWT token error now (or always?). If anyone has a generic example of a node.js script that simply uploads one image to Ghost successfully, I would love to see it. I may be defeated on this one.

Random question, is this simply just not handled via the API?

---

<div class="post-metadata">

**Author:** ![mjw](https://sea1.discourse-cdn.com/flex015/user_avatar/forum.ghost.org/mjw/32/30781_2.png) [@mjw](https://forum.ghost.org/u/mjw)\
**Post date:** [May 18, 2023, 6:09pm UTC](https://forum.ghost.org/t/404-error-on-image-upload/38519/5 "2023-05-18T18:09:09Z")

</div>

Since you’re a Ghost(Pro) customer, you can contact support directly using the email found in the footer.

---

<div class="post-metadata">

**Author:** ![RyanF](https://sea1.discourse-cdn.com/flex015/user_avatar/forum.ghost.org/ryanf/32/17448_2.png) [@RyanF](https://forum.ghost.org/u/RyanF)\
**Post date:** [May 18, 2023, 6:42pm UTC](https://forum.ghost.org/t/404-error-on-image-upload/38519/6 "2023-05-18T18:42:22Z")

</div>

It looks like you’re using the SDK, which means that uploading an image should be as simple as this:

```javascript
const GhostAdminAPI = require("@tryghost/admin-api");
const api = new GhostAdminAPI({
    url: process.env.GHOST_ADMIN_API_URL,
    key: process.env.GHOST_ADMIN_API_KEY,
    version: "v5.0"
});

async function uploadImage() {
  try {
    const res = await api.images.upload({file: "path/to/image"});
    console.log(res); 
   /* expected output: {
        url: 'path to image on Ghost',
        ref: null
        } 
   */
   } catch (e) {
      console.log(e) 
   }
}

uploadImage();

```

---

<div class="post-metadata">

**Author:** ![jannis](https://sea1.discourse-cdn.com/flex015/user_avatar/forum.ghost.org/jannis/32/39718_2.png) [@jannis](https://forum.ghost.org/u/jannis)\
**Post date:** [May 18, 2023, 6:46pm UTC](https://forum.ghost.org/t/404-error-on-image-upload/38519/7 "2023-05-18T18:46:07Z")

</div>

It might be an authentication issue then, not a 404 error.

On that front, I see that you’re trying to use the `GHOST_ADMIN_API_KEY` directly in the Authorization header. However, you first need to create a JWT token from it, as outlined [here](https://ghost.org/docs/admin-api/#token-generation).

I have modified your script a bit and tested it with one of my Ghost instances. It is working now 🙂

```auto
// Import the necessary modules
import axios from 'axios';
import fs from 'fs';
import FormData from 'form-data';
import dotenv from 'dotenv';
import GhostAdminAPI from '@tryghost/admin-api';

// Initialize dotenv
dotenv.config();

// Define Ghost Admin API key and URL
const GHOST_ADMIN_API_KEY = process.env.GHOST_ADMIN_API_KEY;
const GHOST_ADMIN_URL = process.env.GHOST_ADMIN_URL;

// Ensure all necessary environment variables are set
if (!GHOST_ADMIN_API_KEY || !GHOST_ADMIN_URL) {
  console.error(
    'ERROR: Missing necessary environment variables. Please set GHOST_ADMIN_API_KEY and GHOST_ADMIN_URL.'
  );
  process.exit(1);
}

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

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

// Function to upload the image
const uploadImageToGhost = async (imagePath) => {
  try {
    // Check if the file exists
    if (!fs.existsSync(imagePath)) {
      console.error(
        'ERROR: The specified image file does not exist:',
        imagePath
      );
      return;
    }

    // Create a new instance of FormData
    const formData = new FormData();

    // Add the image file to the form data
    formData.append('file', fs.createReadStream(imagePath));

    // Axios configuration for the POST request
    const config = {
      headers: {
        Authorization: `Ghost ${token}`,
        ...formData.getHeaders(), // Spread operator to merge form data headers
      },
    };
    // Send POST request to Ghost API
    const response = await axios.post(
      `${GHOST_ADMIN_URL}/images/upload/`,
      formData,
      config
    );

    // Log the URL of the uploaded image
    console.log(
      'Image uploaded successfully. Image URL:',
      response.data.images[0].url
    );
  } catch (error) {
    console.error(
      'An error occurred while uploading the image:',
      error.response.data
    );
  }
};

uploadImageToGhost('./inputData/georgia.jpg');

```

Another note on it: based on this script you actually don’t need the @tryghost/admin-api module. You could, however, re-write the script to use it – might make things a bit easier in regards to the token generation (since that is handled for you), but add another dependency.

EDIT: @RyanF provided a great example on how to do this with the SDK – now you have two versions and can choose which one you prefer 😃
