vercel/ai · error · APICallError

${error.message}

Error message

${error.message}

What it means

When an Anthropic SSE stream reports a normalized error event, doStream re-throws it as an `APICallError` whose message is the provider's error message (the template `${error.message}`). This surfaces mid-stream provider failures (overloaded, rate limit, invalid request) with status code, headers, and the raw error data attached.

Source

Thrown at packages/anthropic/src/anthropic-language-model.ts:2769

      let result = await firstChunkReader.read();

      // when raw chunks are enabled, the first chunk is a raw chunk, so we need to read the next chunk
      if (result.value?.type === 'raw') {
        result = await firstChunkReader.read();
      }

      // The Anthropic API returns 200 responses when there are overloaded errors.
      // We handle the case where the first chunk is an error here and transform
      // it into an APICallError.
      if (result.value?.type === 'error') {
        const error = result.value.error;

        if (!isProviderStreamError(error)) {
          throw new Error('Expected a normalized Anthropic stream error');
        }

        throw new APICallError({
          message: error.message,
          url,
          requestBodyValues: body,
          statusCode: error.statusCode ?? 500,
          responseHeaders,
          responseBody: JSON.stringify(error.data),
          isRetryable: error.isRetryable ?? false,
        });
      }
    } finally {
      firstChunkReader.cancel().catch(() => {});
      firstChunkReader.releaseLock();
    }

    return {
      stream: streamForConsumer,
      request: { body },
      response: { headers: responseHeaders },

View on GitHub (pinned to 69428b1f8b)

Solutions

  1. Read the resulting APICallError's statusCode/message to identify the provider cause and adjust the request or retry accordingly.
  2. Catch the error via the stream's onError/onFinish callbacks or try/catch around the stream consumption.
  3. Enable retry/backoff for overloaded (529) and rate-limit (429) statuses.
  4. Check `error.data`/responseBody for Anthropic's error type field (overloaded_error, rate_limit_error, etc.).

Example fix

// before
const { textStream } = streamText({ model: anthropic('claude-...'), prompt });
for await (const t of textStream) process.stdout.write(t);
// after
const result = streamText({ model: anthropic('claude-...'), prompt, maxRetries: 3 });
try {
  for await (const t of result.textStream) process.stdout.write(t);
} catch (e) {
  if (APICallError.isInstance(e) && e.statusCode === 529) { /* retry later */ }
  throw e;
}
Defensive patterns

Strategy: try-catch

Validate before calling

null

Type guard

function isAnthropicStreamApiError(e: unknown): e is import('@ai-sdk/provider').APICallError {
  return import('@ai-sdk/provider').APICallError.isInstance(e);
}

Try / catch

try {
  for await (const delta of result.textStream) { /* ... */ }
} catch (e) {
  if (APICallError.isInstance(e)) {
    const { statusCode, message } = e;
    if (statusCode === 529 || statusCode === 429) { /* retry with backoff */ }
  }
  throw e;
}

Prevention

When it happens

Trigger: Consuming `streamText`/stream APIs with an Anthropic model when the upstream stream emits an error event; the error must pass `isProviderStreamError` or a different error ('Expected a normalized Anthropic stream error') is thrown instead.

Common situations: Anthropic returning 529 overloaded_error mid-stream; rate_limit errors on long streams; invalid_request_error from unsupported parameters appearing after stream start; transient network drops interpreted as stream errors.

Related errors


AI-assisted analysis of vercel/ai@69428b1f8b (2026-08-30). Data as JSON: /api/errors/4bcf28382fa7fa30. Report an issue: GitHub.