| name | external-api-client |
| description | Standardizes retries, backoff, timeout, idempotency, and circuit-breaker decisions when consuming third-party APIs. Trigger on "call external API", "retry logic", "rate limiting", "circuit breaker", "HTTP client wrapper". Do NOT use for api-schema (designing APIs), auth-patterns (auth implementation), or mcp-protocol (MCP servers). |
| license | Apache-2.0 |
| compatibility | {"clients":["openai-codex","gemini-cli","opencode","github-copilot"]} |
| metadata | {"owner":"codex","domain":"external-api-client","maturity":"draft","risk":"low","tags":["external","api","client"]} |
Purpose
Build reliable API clients that handle real-world network conditions: transient failures, rate limits, auth token refresh, and timeouts. Following patterns from production APIs like Anthropic's demonstrates what mature clients look like.
When to use this skill
Use when:
- Integrating a new third-party API (payment, AI, cloud services)
- Existing API client has reliability issues (random failures, rate limit errors)
- Building a wrapper/SDK around an external service
- API calls are in critical paths (checkout, data sync)
Do NOT use when:
- Calling internal services with guaranteed SLAs (use simpler client)
- One-off scripts where manual retry is acceptable
Operating procedure
-
Set appropriate timeouts (never use defaults blindly):
client = httpx.Client(
timeout=httpx.Timeout(
connect=5.0,
read=60.0,
write=10.0,
pool=5.0
)
)
-
Implement exponential backoff with jitter:
import random
import time
def retry_with_backoff(fn, max_retries=3, base_delay=1.0):
for attempt in range(max_retries):
try:
return fn()
except (RateLimitError, ServiceUnavailableError) as e:
if attempt == max_retries - 1:
raise
delay = base_delay * (2 ** attempt) + random.uniform(0, 1)
time.sleep(delay)
-
Handle rate limits explicitly:
def call_api(request):
response = client.post("/v1/messages", json=request)
if response.status_code == 429:
retry_after = int(response.headers.get("retry-after", ))
RateLimitError()
response.status_code >= :
ServiceUnavailableError()
response.raise_for_status()
response.json()
Output defaults
class ExternalAPIClient:
"""
Client for [Service Name] API
Features:
- Exponential backoff with jitter (max 3 retries)
- Rate limit handling with Retry-After header
- Automatic token refresh
- Circuit breaker (5 failures = 30s cooldown)
- Configurable timeouts (connect: 5s, read: 60s)
"""
RETRYABLE_STATUS_CODES = {429, 500, 502, 503, 504}
References
Failure handling
- Infinite retry loops: Always set max_retries; log when exhausted
- Rate limit storms: Implement client-side rate limiting; don't rely only on 429 responses
- Auth token refresh race: Use mutex/lock when multiple threads share token
- Timeout too short for operation: Research API's expected latency; AI APIs often need 60s+ read timeout
- Missing idempotency key on retry: Always generate key BEFORE first attempt, reuse on retries