Safely handles API responses to prevent crashes from malformed JSON, HTML error pages, or unexpected response types. Use any time calling API endpoints.
View on GitHubplugins/aai-core/skills/defensive-api-handling/SKILL.md
February 1, 2026
Select agents to install to:
npx add-skill https://github.com/the-answerai/alphaagent-team/blob/main/plugins/aai-core/skills/defensive-api-handling/SKILL.md -a claude-code --skill defensive-api-handlingInstallation paths:
.claude/skills/defensive-api-handling/# Defensive API Handling
**Purpose**: Safely handle API responses to prevent crashes
**When to Use**: Any time you call an API endpoint (in tests or production code)
---
## Never Assume API Returns JSON
APIs can return:
- JSON (expected)
- HTML error pages
- Empty responses
- Malformed JSON
- Non-200 status codes
**Your code must handle ALL of these safely.**
---
## Pattern 1: Safe JSON Parsing
```typescript
async function safeJsonParse(response: Response): Promise<unknown | null> {
// 1. Check status code
if (!response.ok) {
console.warn(`API returned ${response.status} ${response.statusText}`);
return null;
}
// 2. Check content type
const contentType = response.headers.get('content-type');
if (!contentType?.includes('application/json')) {
console.warn(`Expected JSON, got ${contentType}`);
const text = await response.text();
console.warn(`Response body: ${text.slice(0, 200)}`);
return null;
}
// 3. Try to parse
try {
return await response.json();
} catch (error) {
console.warn('Failed to parse JSON:', error.message);
return null;
}
}
```
---
## Pattern 2: Fetch with Timeout
```typescript
async function fetchWithTimeout(
url: string,
options: RequestInit = {},
timeoutMs: number = 10000
): Promise<Response> {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), timeoutMs);
try {
const response = await fetch(url, {
...options,
signal: controller.signal
});
return response;
} finally {
clearTimeout(timeout);
}
}
```
---
## Pattern 3: Retry Logic
```typescript
async function fetchWithRetry(
url: string,
options: RequestInit = {},
maxRetries: number = 3,
delayMs: number = 1000
): Promise<Response | null> {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const response = await fetchWithTimeout(url, options);
// Retry on 5xx errors
if (response.status >= 500 && att