{"record":{"id":"c73951691401ca45","repo":"HeyPuter/puter","slug":"bad-request-c73951","errorCode":"bad_request","errorMessage":"`messages` must be an array of chat messages","messagePattern":"`messages` must be an array of chat messages","errorType":"http","errorClass":"HttpError","httpStatus":400,"severity":"error","filePath":"src/backend/controllers/puterai/PuterAIController.ts","lineNumber":308,"sourceCode":"            const models = await driver.models();\n            const HIDDEN = ['costly', 'fake', 'abuse', 'model-fallback-test-1'];\n            res.json({\n                models: models?.filter((m) => !HIDDEN.includes(m.id)),\n            });\n        };\n    }\n\n    // -- /openai/v1/chat/completions ---------------------------------\n\n    openaiChatCompletions = async (\n        req: Request,\n        res: Response,\n    ): Promise<void> => {\n        const body = asRecord(req.body);\n        const stream = !!body.stream;\n\n        if (!Array.isArray(body.messages)) {\n            throw new HttpError(\n                400,\n                '`messages` must be an array of chat messages',\n                { legacyCode: 'bad_request' },\n            );\n        }\n\n        const completionId = `chatcmpl-${randomId()}`;\n        const created = Math.floor(Date.now() / 1000);\n\n        const completeArgs: ICompleteArguments = {\n            messages: body.messages,\n            model: toStringOrEmpty(body.model),\n            stream,\n            ...(body.tools ? { tools: body.tools as unknown[] } : {}),\n            ...(body.temperature !== undefined\n                ? { temperature: Number(body.temperature) }\n                : {}),\n            ...(body.max_tokens !== undefined","sourceCodeStart":290,"sourceCodeEnd":326,"githubUrl":"https://github.com/HeyPuter/puter/blob/908ec23eda38526170322c3edf71ba45ecb1ca95/src/backend/controllers/puterai/PuterAIController.ts#L290-L326","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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)."],"exampleFix":"// before\nawait fetch(u, { method:'POST', body: JSON.stringify({ model, prompt: text }) });\n// after\nawait fetch(u, { method:'POST',\n  body: JSON.stringify({ model, messages:[{role:'user',content:text}] }) });","handlingStrategy":"validation","validationCode":"function buildChatBody(model, messages) {\n  if (!Array.isArray(messages)) throw new TypeError('messages must be an array');\n  return { model, messages };\n}","typeGuard":"/** @param {unknown} m */\nconst isChatMessages = (m) =>\n  Array.isArray(m) && m.every(x => x && typeof x === 'object' && typeof x.role === 'string' && 'content' in x);","tryCatchPattern":"try { await client.chat.completions.create(body); }\ncatch (e) { if (e?.code === 'bad_request' && /messages/.test(e.message)) fixMessages(); else throw e; }","preventionTips":["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)."],"tags":["ai","api","openai-compatible","validation","bad-request"],"backgroundTag":null,"analyzedSha":"908ec23eda38526170322c3edf71ba45ecb1ca95","analyzedAt":"2026-08-12T20:53:15.911Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}