Fixing “AI_APICallError” After a Vercel AI SDK Upgrade
9 min read · updated August 11, 2026
AI_APICallError is not a bug in the SDK. It is the SDK telling you that the provider rejected the request it built, and the error object contains both the request it sent and the response it got back. Almost every instance of this is solved by printing two fields.
What the error is
The AI SDK wraps every non-success HTTP response from a provider in a single error class. Its name is the string AI_APICallError, which is what you see in a log line or a serialised stack, and the class is exported as APICallError. Vercel’s reference documents the properties it carries: url, requestBodyValues, statusCode, responseHeaders, responseBody, isRetryable, data and cause — see the AI SDK error reference.
The important consequence of that list: the class is provider-agnostic, so the message you see at the top of the stack is generic and the specific complaint is in responseBody. Reading only the message and searching for it is why this error has a reputation for being uninformative. It is one of the more informative errors in the ecosystem, if you open it.
Reading the answer out of the error
import { APICallError } from "ai";
try {
const result = await generateText({ model, prompt });
} catch (error) {
if (APICallError.isInstance(error)) {
console.error({
status: error.statusCode,
url: error.url,
retryable: error.isRetryable,
sent: error.requestBodyValues, // what the SDK actually built
got: error.responseBody, // what the provider said about it
});
}
throw error;
}Use APICallError.isInstance(error) rather than instanceof. The check is a static method for a reason: with multiple copies of the provider package in a dependency tree — exactly the situation an upgrade produces — instanceof compares against a class object that may not be the one the throwing code used, and silently returns false.
Then read the two fields in this order. responseBody tells you what the provider objected to, usually naming a parameter. requestBodyValues tells you what was actually sent, which is frequently not what you think you configured — this is where you discover a renamed option was dropped on the floor rather than passed through. The gap between those two is the bug, in almost every case.
Note the status code as well, because it partitions the causes cleanly. A 401 or 403 is credentials or a base URL pointing somewhere unexpected. A 404 is a model string that does not exist on that provider. A 400 is the request shape, which is the interesting case and the rest of this page. A 429 or a 5xx is not an upgrade problem at all and isRetryable will be true.
The error people confuse it with
If your upgrade crossed a major version, there is a second error that looks similar in a log and has a completely different cause:
AI SDK 5 only supports models that implement specification version "v2".
That is not an API call error — no request was made. It means the core package and a provider package are on incompatible majors: the core expects a provider implementing the current language-model specification, and the installed provider still implements the previous one. Vercel’s troubleshooting note for it says to bring the core and every @ai-sdk/* provider package up together rather than one at a time — the unsupported model version note.
The practical rule is that core and providers move as a set. Upgrading ai alone, or updating one provider because a lockfile resolved it, is the single most common way to end up in this state. Check for duplicate copies of the provider interface package in the tree before you debug anything else; a nested duplicate produces both this error and the instanceof problem above.
What the upgrade changed
Once you have a genuine 400 with a body, these are the changes that produce one. Each is a case where the SDK stopped sending something, or started sending something new, without your code changing.
- A renamed option is now silently ignored. The option controlling the output token cap was renamed between majors —
maxTokenstomaxOutputTokens. Passing the old name in TypeScript is a type error; passing it from an object built at runtime, from config or from JSON, is not, and the cap simply stops being applied. That is not a 400 by itself, but it produces one on any provider that requires a token cap, and it produces a surprise bill everywhere else. - Provider-specific options moved. Options that are not part of the common interface are passed through a dedicated field rather than mixed into the top-level call. If yours were in the wrong place after an upgrade they were either dropped or forwarded verbatim to the provider, and forwarding an unknown key is exactly what produces a 400 naming a parameter you did not knowingly send.
- Message types changed. The UI-facing message type and the model-facing message type became distinct, with a conversion function between them. Sending UI messages straight to a model yields a request whose message entries have the wrong internal shape, and providers reject that with a schema complaint about
messages. - The model string is stale. Upgrades often coincide with copying a new example, and the example names a model your key is not entitled to. This is a 404 or a 403 with a very clear body, and it is worth checking before anything structural.
- A custom base URL provider. If you were pointing the OpenAI provider at a non-OpenAI endpoint by overriding its base URL, check whether that is still the supported route or whether an explicitly OpenAI-compatible provider factory is now the intended one. A compatible endpoint receiving a field the official provider newly started sending will reject it — see what compatible endpoints do with parameters they do not support.
Handling it properly afterwards
- Reproduce outside the framework. Take
urlandrequestBodyValuesfrom the error, replay them withcurl, and confirm you get the same 400. If you do, the SDK is not involved and you are debugging a request body. If you do not, the difference is a header, andresponseHeadersplus your provider configuration is where to look. - Fix the field, then pin the versions. Bring
aiand every@ai-sdk/*package to compatible majors in one change and commit the lockfile. - Add the catch block above to your call sites permanently, logging status, url and response body. Do not log
requestBodyValuesunredacted in production — it contains the full prompt, which is user data. - Split retryable from non-retryable at the boundary.
isRetryableis derived from the status code, so it will not tell you a 400 is worth retrying — and it is not. Retrying a malformed request is how a deploy-time bug becomes a rate-limit incident. - Add one integration test that makes a real call against each provider you use, running on your dependency-update branch. Every cause on this page fails that test immediately and none of them fail a unit test with a mocked provider.
That last step is the one that pays for itself. The reason this error arrives after an upgrade rather than during development is that the request body is constructed by code you do not own, from options you set months ago, against a schema on a server you cannot see. Nothing short of a real call exercises that path.