The fix: move the tools array INSIDE the model object. Vapi rejects top-level tools on assistant create/update with 400: property tools should not exist.

Symptom

Creating or updating an assistant via POST /assistant or PATCH /assistant/:id returns HTTP 400 with property tools should not exist, even though the docs describe tool calling.

The wrong payload

{ "name": "Reception Agent",
  "model": { "provider": "openai", "model": "gpt-4o" },
  "tools": [ { "type": "function", ... } ] }

The correct payload

{ "name": "Reception Agent",
  "model": { "provider": "openai", "model": "gpt-4o",
             "tools": [ { "type": "function", ... } ] } }

Why it happens

Vapi treats tools as a property of the model configuration, not of the assistant. Most SDK examples floating around show the top-level form from an older schema, which is why this 400 keeps appearing in production integrations.

How we hit it

Building a missed-call recovery voice agent for a trades client (Twilio number imported into Vapi, tool call posting lead data to n8n). The assistant create call failed only when tools were attached; every other field validated fine.

Verify

The create call returns 201 and the assistant JSON echoes your tools under model.tools. While you are in there: tool-call arguments arrive as a JSON string, so JSON.parse() before use, and reply with {"results":[{"toolCallId":"...","result":"..."}]}.

Related landmines

Vapi 403 error code 1010 (Cloudflare) · All Vapi landmines

We design, build and run voice agents like this white-label for marketing agencies. What we build →