How to Handle OpenAI API Errors in TypeScript
By Nark Team
The OpenAI TypeScript SDK (openai) throws typed errors when API calls fail. The most common are RateLimitError (429 responses), AuthenticationError (invalid API key), APIConnectionError (network failure), and BadRequestError (malformed input). If your code calls openai.chat.completions.create() without a try-catch, any of these errors crashes your endpoint and your AI feature silently breaks. Most AI-powered TypeScript applications have this bug because the happy path works fine during development.
Quick Answer: Wrap every
openai.chat.completions.create()call in a try-catch. CatchRateLimitErrorto implement backoff/retry. CatchAuthenticationErrorto surface config problems early. CatchAPIConnectionErrorfor network failures. To verify your entire codebase handles OpenAI errors:npx nark --tsconfig ./tsconfig.json
What Errors Does the OpenAI SDK Throw?
The openai package (v4+) exports specific error classes from openai:
| Error Class | HTTP Status | When It Happens |
|---|---|---|
RateLimitError | 429 | Too many requests or quota exceeded |
AuthenticationError | 401 | Invalid or missing API key |
PermissionDeniedError | 403 | API key lacks access to the model |
BadRequestError | 400 | Invalid parameters (bad model name, too many tokens) |
NotFoundError | 404 | Model or resource does not exist |
UnprocessableEntityError | 422 | Request understood but cannot be processed |
APIConnectionError | N/A | Network failure, DNS error, timeout |
InternalServerError | 500+ | OpenAI server error |
APIError | Any | Base class for all API errors |
Every one of these is thrown as an exception. If your code does not catch it, Express/Next.js/Fastify returns a 500 to the user.
The Wrong Way: No Error Handling
This is the pattern most AI tutorials and Copilot-generated code produces:
import OpenAI from 'openai';
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
async function generateSummary(text: string): Promise<string> {
const response = await openai.chat.completions.create({
model: 'gpt-4o',
messages: [{ role: 'user', content: `Summarize: ${text}` }],
});
return response.choices[0].message.content ?? '';
}
This works during development. In production, when OpenAI rate-limits you during a traffic spike, RateLimitError is thrown. The function crashes. The API endpoint returns 500. The user sees "Internal Server Error" instead of a meaningful message.
The Right Way: Handle Each Error Type
import OpenAI from 'openai';
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
async function generateSummary(text: string): Promise<string> {
try {
const response = await openai.chat.completions.create({
model: 'gpt-4o',
messages: [{ role: 'user', content: `Summarize: ${text}` }],
});
return response.choices[0].message.content ?? '';
} catch (error) {
if (error instanceof OpenAI.RateLimitError) {
// 429 — too many requests or quota exceeded
// Option 1: retry with backoff
// Option 2: return a fallback response
throw new AppError(429, 'AI service is busy. Please try again in a moment.');
}
if (error instanceof OpenAI.AuthenticationError) {
// 401 — API key is invalid or missing
// This is a config problem, not a user problem
console.error('OpenAI API key is invalid');
throw new AppError(500, 'AI service configuration error');
}
if (error instanceof OpenAI.APIConnectionError) {
// Network failure — DNS, timeout, connection refused
console.error('Cannot reach OpenAI API:', error.message);
throw new AppError(503, 'AI service is temporarily unavailable');
}
if (error instanceof OpenAI.BadRequestError) {
// 400 — bad model name, too many tokens, invalid params
console.error('OpenAI bad request:', error.message);
throw new AppError(400, 'Invalid AI request parameters');
}
// Unknown error — re-throw with context
throw error;
}
}
Each error type gets a different response. Rate limits get a 429 with a retry hint. Auth errors get a 500 with a log for the ops team. Network errors get a 503. Bad requests get a 400.
How to Implement Retry with Backoff for Rate Limits
Rate limits are the most common OpenAI error in production. The correct response is exponential backoff:
async function withRetry<T>(
fn: () => Promise<T>,
maxRetries: number = 3,
): Promise<T> {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
return await fn();
} catch (error) {
if (error instanceof OpenAI.RateLimitError && attempt < maxRetries) {
const delay = Math.pow(2, attempt) * 1000; // 1s, 2s, 4s
await new Promise((resolve) => setTimeout(resolve, delay));
continue;
}
throw error;
}
}
throw new Error('Unreachable');
}
// Usage
const summary = await withRetry(() =>
openai.chat.completions.create({
model: 'gpt-4o',
messages: [{ role: 'user', content: `Summarize: ${text}` }],
}),
);
The OpenAI SDK also has built-in retry support via the maxRetries constructor option:
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
maxRetries: 3, // retries 429 and 5xx errors automatically
});
If you set maxRetries, the SDK handles backoff for RateLimitError and server errors automatically. You still need a try-catch for AuthenticationError, BadRequestError, and APIConnectionError, which are not retried.
Handling Streaming Responses
Streaming (stream: true) has different error behavior. Errors can occur mid-stream:
async function streamSummary(text: string): Promise<string> {
try {
const stream = await openai.chat.completions.create({
model: 'gpt-4o',
messages: [{ role: 'user', content: `Summarize: ${text}` }],
stream: true,
});
let result = '';
for await (const chunk of stream) {
const content = chunk.choices[0]?.delta?.content;
if (content) result += content;
}
return result;
} catch (error) {
if (error instanceof OpenAI.APIError) {
console.error(`OpenAI stream error [${error.status}]:`, error.message);
throw new AppError(error.status ?? 500, 'AI streaming failed');
}
throw error;
}
}
The initial create() call can throw before any data arrives. The for await loop can also throw if the connection drops mid-stream. The try-catch around the entire block handles both cases.
The Anthropic SDK Has the Same Pattern
If you also use the Anthropic SDK (@anthropic-ai/sdk), the error handling pattern is identical:
import Anthropic from '@anthropic-ai/sdk';
const anthropic = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
try {
const response = await anthropic.messages.create({
model: 'claude-sonnet-4-20250514',
max_tokens: 1024,
messages: [{ role: 'user', content: 'Summarize this text...' }],
});
} catch (error) {
if (error instanceof Anthropic.RateLimitError) {
// 429 — back off and retry
}
if (error instanceof Anthropic.AuthenticationError) {
// 401 — bad API key
}
if (error instanceof Anthropic.APIConnectionError) {
// Network failure
}
}
Nark has profiles for both OpenAI and Anthropic SDKs. Both check for the same category of missing error handling.
How Nark Catches Missing OpenAI Error Handling
Nark's profile for the openai package checks whether calls to openai.chat.completions.create() and other API methods are wrapped in error handling. If not, Nark flags it:
ERROR openai openai.chat.completions.create() called without try-catch
src/ai/summarize.ts:8 in generateSummary()
OpenAI SDK throws RateLimitError, AuthenticationError, APIConnectionError
Fix: wrap in try-catch, handle RateLimitError for retry logic
This catches the exact bug that most AI-powered applications ship with: the happy path works, but the first 429 response crashes the feature.
Frequently Asked Questions
Does the OpenAI SDK retry automatically?
By default, yes. The SDK retries 429 and 5xx errors up to 2 times with exponential backoff. You can change this with the maxRetries constructor option. But AuthenticationError, BadRequestError, and APIConnectionError are never retried. You need a try-catch for those.
What is the difference between APIError and RateLimitError?
APIError is the base class. RateLimitError, AuthenticationError, BadRequestError, etc. all extend APIError. Catching APIError catches all of them. Catching specific subclasses lets you handle each differently.
Does TypeScript's type system warn about unhandled OpenAI errors?
No. TypeScript does not have checked exceptions. The type signature of openai.chat.completions.create() does not indicate that it throws. The only way to know is to read the documentation or use a tool like Nark that encodes this knowledge.
What about the Azure OpenAI SDK?
The Azure OpenAI client (@azure/openai) throws different error types but the same principle applies: wrap API calls in try-catch and handle rate limits, auth errors, and network failures. Nark's profile coverage for Azure OpenAI is in progress.
Try It Now
npx nark --tsconfig ./tsconfig.json
Nark checks 160+ packages — including axios, prisma, stripe, redis, and the OpenAI and Anthropic SDKs — for correct error handling. Run it on your AI-powered TypeScript project to find unhandled API errors before they silently break your features in production.