400 BAD_REQUEST error when passing audio to Server before passing to OpenAI

I have a frontend that allows users to upload a file which then gets sent to my Node/Express server in a multipart form.

I am using multer to parse the file from the request but I can’t pass it to OpenAI without error. I have it set to save the file in storage and then I pass the file path into the request and it returns with an error.

The error I get is "Request failed with status code 400"

const express = require("express");
const multer = require("multer");
const { Configuration, OpenAIApi } = require("openai");
const upload = multer({ dest: "uploads/" });
const fs = require("fs");
const whisperConfiguration = new Configuration({
  apiKey: "API_KEY",
});
const app = express();

const port = 3000;
app.use(express.static("static"));


app.post("/whisper", upload.single("audio_file"), async (req, res) => {
  const whisperTime = process.hrtime();
  const openai = new OpenAIApi(whisperConfiguration);
  console.log("whisper", req.file);

  try {
    const resp = await openai.createTranscription(
      fs.createReadStream(req.file.path),
      "whisper-1"
    );
    res.send({ apiCall: resp.data, time: process.hrtime(whisperTime) });
  } catch (e) {
    res.send(e);
  }
});

app.listen(port, () => console.log(`Example app listening on port ${port}!`));

I have tried this same code without reading the file from the request, but reading it from a locally stored file and it works fine. So I know there isn’t an authentication issue.

I have only got it to work by sending it the bytes, either from local disc or local memory.

@curt.kennedy I can get the Buffer from the multer middleware. I’ve tried sending just the buffer but that didn’t work either. Is there something else I could do with the Buffer?

1 Like

Here is a quick simple Python version I coded after the announcement. Works out of the gate in an AWS Lambda environment (just add a requests layer and path to the actual file). The memory version using a BytesIO object would be similar. Haven’t tried any JavaScript versions myself.

Okay I got it to work. The issue is with the openai npm package. I did it straight through an axios request and it works fine.

app.post("/whisper", whisperUpload.single("audio_file"), async (req, res) => {
  const whisperTime = process.hrtime();
  console.log("whisper", req.file);

  try {
    const formData = new FormData();
    formData.append("model", "whisper-1");
    formData.append("file", fs.createReadStream(req.file.path), {
      filename: req.file.originalname,
    });
    const resp = await axios.post(
      "https://api.openai.com/v1/audio/transcriptions",
      formData,
      {
        headers: {
          "Content-Type": "multipart/form-data",
          Authorization:
            "Bearer API_KEY",
        },
      }
    );

    res.send({ apiCall: resp.data, time: process.hrtime(whisperTime) });
  } catch (e) {
    console.log("error", e);
    res.send(new Error(e));
  }
});
2 Likes

Interesting. That’s why I created the standalone Python version. The OpenAI library didn’t work for me at the time.

2 Likes

Thanks to all.
I had today the same issue with the nodejs library. I created a nodejs ( typescript ) version of your solution and that worked.

Thank you.

Here is a short snippet in nodejs. Maybe its helpfull for someone.

  import * as fs from "fs";
            import FormData from "form-data";
            import axios from "axios";

            const apiK = process.env.OPENAI_API_KEY;
            let fileBuffer = fs.readFileSync(yourfilepath)
            const headers = {
                'Authorization': `Bearer ${apiK}`,
            };

            const form = new FormData();
            form.append('file', fileBuffer, {filename: 'audio.webm', contentType: 'audio/webm'})
            form.append('model', 'whisper-1');

            axios.post('https://api.openai.com/v1/audio/transcriptions', form,
                {
                    headers: {...form.getHeaders(), ...headers}
                }
            )
                .then(response => {
                    console.log(response.data);
                })
                .catch(error => {
                    console.error(error);
                });

Hi there! I was experiencing the same issue, but I found a solution when using the node.js library or fetch:

Initially, I was doing this:

 const file = await toFile(Buffer.from(data));

Somehow, I managed to make it work without the library, and then I asked myself: WHY?!

Here is the reason:

 const file = await toFile(Buffer.from(data), 'audio.mp3');

This worked for both the node.js library and a pure fetch request.

I’m not really sure why, but I believe it has to do with the “name” inference in the “toFile()” method:

@param name — the name of the file. If omitted, toFile will try to determine a file name from bits if possible.

Note it says Try

When I manually added the name and type, it worked properly. However, when I removed it, I started getting nonstop errors.

Partly, I think this happened because I am fetching the file from a URL on the internet hosted by an external service, and from there, I am creating a Buffer. Perhaps some information got lost in the process, and it probably shouldn’t be an issue if you simply retrieve a file that is submitted from a form on the frontend.

Here is my complete code, just for reference:
(It is a Next.ts 14 API route.ts)

import { prisma } from '@/lib/prisma';
import { NextResponse } from 'next/server';
import axios from 'axios';
import { z } from 'zod';
import { openAiApi } from '@/lib/openai';
import { toFile } from 'openai/uploads';

const paramsSchema = z.object({
  id: z.string().uuid(),
});
const bodySchema = z.object({
  prompt: z.string(),
});
export async function POST(
  request: Request,
  context: { params: { id: string } }
) {
  const body = await request.json();
  try {
    const { id } = paramsSchema.parse(context.params);
    const { prompt } = bodySchema.parse(body);

    const video = await prisma.video.findUniqueOrThrow({ where: { id } });

    const { data } = await axios.get(video.path, {
      responseType: 'arraybuffer',
    });
    const file = await toFile(Buffer.from(data), 'audio.mp3');

    const transcription = await openAiApi.audio.transcriptions.create({
      file: file,
      model: 'whisper-1',
      language: 'en',
      response_format: 'verbose_json',
      temperature: 0.1,
      prompt,
    });

    return NextResponse.json({ id, prompt, transcription }, { status: 200 });
  } catch (err) {
    if (err instanceof z.ZodError) {
      return NextResponse.json({ error: err.issues }, { status: 400 });
    }
    return NextResponse.json({ error: err }, { status: 500 });
  }
}

Spent two days trying to figure this out. In the end, I had to use a ‘multipart/form-data’ class to encode the request; however, when I sent the encoded data, I had to omit the ‘multipart/form-data’ type from the POST request. I hope this will help others save time.

Update: My code was overriding the ‘Content-Type’; for example, I set the following:

headers->Insert("Content-Type", "multipart/form-data; boundary={$boundry}");

By passing multipart/form-data to POST the Content-Type header was being overridden and the boundary parameter was missing.