[ PROMPT_NODE_24701 ]
Network
[ SKILL_DOCUMENTATION ]
# Network & API Error Patterns
Common network and API errors with diagnosis and solutions.
## HTTP Status Errors
### 400 Bad Request
```
HTTP 400 Bad Request
{"error": "Bad Request", "message": "Invalid JSON"}
```
**Causes**:
1. Malformed JSON body
2. Missing required fields
3. Invalid field values
4. Wrong Content-Type header
**Diagnosis**:
```bash
# Validate JSON
echo '{"key": "value"}' | jq .
# Check request
curl -v -X POST https://api.example.com/endpoint
-H "Content-Type: application/json"
-d '{"key": "value"}'
```
**Solutions**:
```javascript
// Check Content-Type
fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json', // Not 'text/plain'
},
body: JSON.stringify(data), // Not just data
})
// Validate before sending
if (!data.required_field) {
throw new Error('Missing required_field')
}
```
---
### 401 Unauthorized
```
HTTP 401 Unauthorized
{"error": "Unauthorized", "message": "Invalid or expired token"}
```
**Causes**:
1. Missing auth token
2. Expired token
3. Invalid token format
4. Wrong auth scheme
**Solutions**:
```javascript
// Check Authorization header format
fetch(url, {
headers: {
'Authorization': `Bearer ${token}`, // Note: "Bearer " prefix
},
})
// Refresh token if expired
async function fetchWithAuth(url) {
let response = await fetch(url, {
headers: { 'Authorization': `Bearer ${accessToken}` }
})
if (response.status === 401) {
accessToken = await refreshToken()
response = await fetch(url, {
headers: { 'Authorization': `Bearer ${accessToken}` }
})
}
return response
}
```
---
### 403 Forbidden
```
HTTP 403 Forbidden
{"error": "Forbidden", "message": "Insufficient permissions"}
```
**Causes**:
1. Authenticated but not authorized
2. Resource access denied
3. Rate limiting
4. IP blocked
**Different from 401**: 401 = "Who are you?" 403 = "I know who you are, but no."
**Solutions**:
- Check user permissions/roles
- Verify API key scopes
- Check rate limit headers
- Verify IP whitelist
---
### 404 Not Found
```
HTTP 404 Not Found
{"error": "Not Found", "message": "Resource not found"}
```
**Causes**:
1. Wrong URL/endpoint
2. Resource deleted
3. ID doesn't exist
4. Trailing slash issues
**Diagnosis**:
```bash
# Check URL is correct
curl -I https://api.example.com/users/123
# Try with/without trailing slash
curl https://api.example.com/users/
curl https://api.example.com/users
```
**Solutions**:
```javascript
// Handle 404 gracefully
const response = await fetch(`/api/users/${id}`)
if (response.status === 404) {
return null // Or throw custom error
}
return response.json()
```
---
### 405 Method Not Allowed
```
HTTP 405 Method Not Allowed
{"error": "Method Not Allowed", "allowed": ["GET", "POST"]}
```
**Causes**:
1. Using wrong HTTP method
2. Endpoint doesn't support method
**Solutions**:
```bash
# Check allowed methods
curl -I -X OPTIONS https://api.example.com/endpoint
# Look for Allow header
# Use correct method
curl -X POST https://api.example.com/users # Not GET
```
---
### 429 Too Many Requests
```
HTTP 429 Too Many Requests
{"error": "Rate Limited", "retry_after": 60}
```
**Causes**:
1. Rate limit exceeded
2. Too many concurrent requests
**Solutions**:
```javascript
// Implement exponential backoff
async function fetchWithRetry(url, maxRetries = 3) {
for (let i = 0; i new Promise(resolve => setTimeout(resolve, ms))
// Retry with exponential backoff
async function fetchWithBackoff(url) {
const maxRetries = 5
for (let i = 0; i controller.abort(), 30000)
try {
const response = await fetch(url, { signal: controller.signal })
clearTimeout(timeoutId)
return response
} catch (error) {
if (error.name === 'AbortError') {
throw new Error('Request timed out')
}
throw error
}
```
---
### ECONNRESET
```
Error: read ECONNRESET
```
**Causes**:
1. Server closed connection unexpectedly
2. Network interruption
3. Server crashed
**Solutions**:
```javascript
// Implement retry logic
async function fetchWithRetry(url, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
return await fetch(url)
} catch (error) {
if (error.code === 'ECONNRESET' && i {
res.header('Access-Control-Allow-Origin', 'https://myapp.com')
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE')
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization')
res.header('Access-Control-Allow-Credentials', 'true')
next()
})
```
**Solutions (Client-side workarounds)**:
```javascript
// Use proxy in development
// vite.config.js
export default {
server: {
proxy: {
'/api': {
target: 'https://api.example.com',
changeOrigin: true,
}
}
}
}
// Then fetch from /api instead
fetch('/api/endpoint')
```
---
### Preflight Request Failed
```
Response to preflight request doesn't pass access control check
```
**Causes**:
1. OPTIONS request not handled
2. Missing preflight headers
**Solutions**:
```javascript
// Handle OPTIONS request
app.options('*', cors()) // Express with cors
// Or manually
app.options('*', (req, res) => {
res.header('Access-Control-Allow-Origin', '*')
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization')
res.sendStatus(200)
})
```
---
## JSON Parsing Errors
### Unexpected token < in JSON
```
SyntaxError: Unexpected token '<', "<!DOCTYPE "... is not valid JSON
```
**Causes**:
1. Server returned HTML instead of JSON (error page, 404)
2. Wrong endpoint
3. Auth redirect
**Diagnosis**:
```javascript
const response = await fetch(url)
console.log('Status:', response.status)
console.log('Content-Type:', response.headers.get('content-type'))
const text = await response.text()
console.log('Body:', text.substring(0, 200))
```
**Solutions**:
```javascript
// Check response before parsing
const response = await fetch(url)
if (!response.ok) {
const text = await response.text()
throw new Error(`HTTP ${response.status}: ${text}`)
}
const contentType = response.headers.get('content-type')
if (!contentType?.includes('application/json')) {
throw new Error(`Expected JSON, got ${contentType}`)
}
return response.json()
```
---
### Unexpected end of JSON input
```
SyntaxError: Unexpected end of JSON input
```
**Causes**:
1. Empty response body
2. Truncated response
3. Network interruption
**Solutions**:
```javascript
// Check for empty response
const text = await response.text()
if (!text) {
return null // Or default value
}
return JSON.parse(text)
```
---
## Quick Reference Table
| Error | Category | Quick Fix |
|-------|----------|-----------|
| 400 Bad Request | Client | Check JSON format, Content-Type |
| 401 Unauthorized | Auth | Check token, refresh if expired |
| 403 Forbidden | Auth | Check permissions, rate limits |
| 404 Not Found | Client | Verify URL, check resource exists |
| 429 Too Many Requests | Rate Limit | Implement backoff, reduce requests |
| 500 Internal Server Error | Server | Retry, report to provider |
| 503 Service Unavailable | Server | Retry with exponential backoff |
| ECONNREFUSED | Connection | Start server, check port |
| ENOTFOUND | DNS | Check hostname, DNS settings |
| ETIMEDOUT | Timeout | Increase timeout, check network |
| CORS blocked | Browser | Add CORS headers server-side |
| Unexpected token < | Parse | Check response is JSON not HTML |
Source: claude-code-templates (MIT). See About Us for full credits.