HeyPuter/puter · error · HttpError

bad_request

bad_request

Error message

`messages` must be an array of chat messages

What it means

Returned (HTTP 400, legacy code bad_request) by POST /puterai/openai/v1/chat/completions (PuterAIController.openaiChatCompletions). The route is an OpenAI-compatible proxy on top of the puter-chat-completion driver; it requires the request body to contain a `messages` array, exactly like OpenAI's chat completions API. Any non-array value (string, object, undefined) is rejected before the driver is called.

Source

Thrown at src/backend/controllers/puterai/PuterAIController.ts:308

            const models = await driver.models();
            const HIDDEN = ['costly', 'fake', 'abuse', 'model-fallback-test-1'];
            res.json({
                models: models?.filter((m) => !HIDDEN.includes(m.id)),
            });
        };
    }

    // -- /openai/v1/chat/completions ---------------------------------

    openaiChatCompletions = async (
        req: Request,
        res: Response,
    ): Promise<void> => {
        const body = asRecord(req.body);
        const stream = !!body.stream;

        if (!Array.isArray(body.messages)) {
            throw new HttpError(
                400,
                '`messages` must be an array of chat messages',
                { legacyCode: 'bad_request' },
            );
        }

        const completionId = `chatcmpl-${randomId()}`;
        const created = Math.floor(Date.now() / 1000);

        const completeArgs: ICompleteArguments = {
            messages: body.messages,
            model: toStringOrEmpty(body.model),
            stream,
            ...(body.tools ? { tools: body.tools as unknown[] } : {}),
            ...(body.temperature !== undefined
                ? { temperature: Number(body.temperature) }
                : {}),
            ...(body.max_tokens !== undefined

View on GitHub (pinned to 908ec23eda)

Solutions

  1. Send body as { model, messages: [{role:'user',content:'...'}] }.
  2. If you have a single prompt string, use the /puterai/openai/v1/completions endpoint (prompt field) instead.
  3. Validate with Array.isArray(messages) before the request.
  4. Ensure JSON Content-Type and that the body is actually parsed (not double-stringified).

Example fix

// before
await fetch(u, { method:'POST', body: JSON.stringify({ model, prompt: text }) });
// after
await fetch(u, { method:'POST',
  body: JSON.stringify({ model, messages:[{role:'user',content:text}] }) });
Defensive patterns

Strategy: validation

Validate before calling

function buildChatBody(model, messages) {
  if (!Array.isArray(messages)) throw new TypeError('messages must be an array');
  return { model, messages };
}

Type guard

/** @param {unknown} m */
const isChatMessages = (m) =>
  Array.isArray(m) && m.every(x => x && typeof x === 'object' && typeof x.role === 'string' && 'content' in x);

Try / catch

try { await client.chat.completions.create(body); }
catch (e) { if (e?.code === 'bad_request' && /messages/.test(e.message)) fixMessages(); else throw e; }

Prevention

When it happens

Trigger: POSTing to /puterai/openai/v1/chat/completions with body.messages omitted, set to a plain string, a single object, or null. Also when a client sends `prompt` (legacy completions shape) instead of `messages` to the chat endpoint.

Common situations: Pointing the OpenAI SDK's completions (not chat) call at the chat URL; hand-rolling fetch and forgetting the messages wrapper; sending {input} or {prompt} because of confusion between /v1/completions, /v1/responses, and /v1/chat/completions.

Related errors


AI-assisted analysis of HeyPuter/puter@908ec23eda (2026-08-12). Data as JSON: /api/errors/c73951691401ca45. Report an issue: GitHub.