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 !== undefinedView on GitHub (pinned to 908ec23eda)
Solutions
- Send body as { model, messages: [{role:'user',content:'...'}] }.
- If you have a single prompt string, use the /puterai/openai/v1/completions endpoint (prompt field) instead.
- Validate with Array.isArray(messages) before the request.
- 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
- Always wrap user input in [{role:'user',content:String(input)}].
- Use Array.isArray on messages before sending.
- Pick the endpoint that matches your payload shape (chat vs completions).
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.