Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
A direct command skips the review prompt. Inspect the source before running it.
Diagnoses and recovers from complex REST and GraphQL API failures. Enforces reproduction before fixing and verifies via runtime inspection.
hints
- Systematically isolate the failing networking layer from DNS to application semantics before patching.
- Inspect GraphQL response payloads closely for inner "errors" arrays even when status is 200 OK.
- Differentiate between connection timeouts (firewalls/network) and read timeouts (slow server compute).
- Capture and log vendor-specific request tracking IDs (e.g. X-Request-Id) to streamline external support.
- Build reproducer curl commands or Vitest regression scripts to isolate and confirm fixed behaviors.
API Testing & Debugging
Drive REST and GraphQL diagnosis through standard terminal tools (curl, nslookup, openssl) and script execution. Isolate the failing layer before guessing at the fix.
When to Use
API returns unexpected status or body
Auth fails (401/403 after token refresh, OAuth, API key)
Works in Postman but fails in code
Webhook / callback integration debugging
Building or reviewing API integration tests
Rate limiting or pagination issues
Skip for UI rendering, DB query tuning, or DNS/firewall infra (escalate).
Core Principle
Isolate the layer, then fix. A 200 OK can hide broken data. A 500 can mask a one-character auth typo. Walk the chain in order; never skip a step.
1. Connectivity → can we reach the host at all?
1.5 Timeouts → connect-slow vs read-slow?
2. TLS/SSL → cert valid and trusted?
3. Auth → credentials correct and unexpired?
4. Request format → payload shape match server expectations?
5. Response parse → does our code accept what came back?
6. Semantics → does the data mean what we assume?
Failures: HTML error page where JSON expected, empty body, wrong charset.
Step 6 — Semantic Validation
Parsed cleanly — but is the data correct?
Does "status": "active" mean what your code thinks?
ID in response matches the one requested?
Timestamps in expected timezone?
Pagination returning all results, or just page 1?
HTTP Status Playbook
401 Unauthorized — credentials missing or invalid
Authorization header actually present? (curl -v to confirm)
Token correct and unexpired?
Right auth scheme? (Bearer vs Basic vs Token)
Some APIs use query param (?api_key=…) instead of header.
403 Forbidden — authenticated but not authorized
Token has the required scopes/permissions?
Resource owned by a different account?
IP allowlist blocking you?
CORS in browser? (check Access-Control-Allow-Origin)
404 Not Found — resource doesn't exist or URL is wrong
Path correct? (trailing slash, typo, version prefix)
Resource ID exists?
Right API version (/v1/ vs /v2/)?
Right base URL (staging vs prod)?
409 Conflict — state collision
Resource already exists (duplicate create)?
Stale ETag / If-Match?
Concurrent modification by another process?
422 Unprocessable Entity — valid JSON, invalid data
The error body usually names the bad fields. Check:
Field types (string vs int, date format)
Required vs optional
Enum values inside the allowed set
429 Too Many Requests — rate limited
Check Retry-After and X-RateLimit-* headers. Exponential backoff:
execute_code('''
import time, requests
def with_backoff(method, url, **kwargs):
for attempt in range(5):
resp = requests.request(method, url, **kwargs)
if resp.status_code != 429:
return resp
wait = int(resp.headers.get("Retry-After", 2 ** attempt))
time.sleep(wait)
return resp
''')
5xx — server-side, usually not your fault
500 — server bug. Capture correlation ID, file with provider.
502 — upstream down. Backoff + retry.
503 — overloaded / maintenance. Check status page.
504 — upstream timeout. Reduce payload or raise timeout.
For all 5xx: backoff with jitter, alert on persistence.
Pagination & Idempotency
Pagination. Verify you're getting all results. Look for next_cursor, next_page, total_count. Two patterns:
Offset (?limit=100&offset=200) — simple, can skip items if data shifts.
Cursor (?cursor=abc123) — preferred for live or large datasets.
Idempotency. For non-idempotent operations (POST), send Idempotency-Key: <uuid> so retries don't double-charge / double-create. Mandatory for payments and orders.
Contract Validation
Catch schema drift before it hits production:
execute_code('''
import requests
def validate_user(data: dict) -> list[str]:
errors = []
required = {"id": int, "email": str, "created_at": str}
for field, expected in required.items():
if field not in data:
errors.append(f"missing field: {field}")
elif not isinstance(data[field], expected):
errors.append(f"{field}: want {expected.__name__}, got {type(data[field]).__name__}")
return errors
resp = requests.get(f"{BASE}/users/1", headers=HEADERS, timeout=10)
issues = validate_user(resp.json())
if issues:
print(f"contract violations: {issues}")
''')
Run after API upgrades, when integrating new third parties, or in CI smoke tests.
Correlation IDs
Always capture the provider's request ID — fastest path to vendor support:
execute_code('''
import requests
resp = requests.post(url, json=payload, headers=headers, timeout=10)
request_id = (
resp.headers.get("X-Request-Id")
or resp.headers.get("X-Trace-Id")
or resp.headers.get("CF-Ray") # Cloudflare
)
if resp.status_code >= 400:
print(f"failed status={resp.status_code} req_id={request_id} ts={resp.headers.get('Date')}")
''')
Vendor bug-report template:
Endpoint: POST /api/v1/orders
Request ID: req_abc123xyz
Timestamp: 2026-03-17T14:30:00Z
Status: 500
Expected: 201 with order object
Actual: 500 {"error":"internal server error"}
Repro: curl -X POST … (auth: <REDACTED>)
Regression Test Template
Drop this into tests/ and run via terminal('pytest tests/test_api_smoke.py -v'):
When debugging spans auth → fetch → paginate → validate, use execute_code. Variables persist for the script, results print to stdout, no risk of token spam in your context:
execute_code('''
import os, requests
token = os.environ["API_TOKEN"]
base = "https://api.example.com"
H = {"Authorization": f"Bearer {token}"}
# 1. auth
me = requests.get(f"{base}/me", headers=H, timeout=10)
print(f"auth {me.status_code}")
# 2. paginate
all_users, cursor = [], None
while True:
params = {"cursor": cursor} if cursor else {}
r = requests.get(f"{base}/users", headers=H, params=params, timeout=10)
body = r.json()
all_users.extend(body["data"])
cursor = body.get("next_cursor")
if not cursor:
break
print(f"users={len(all_users)}")
''')
web_extract — for vendor API docs
Pull the spec for the endpoint you're debugging instead of guessing:
delegate_task(
goal="Test all CRUD endpoints for /api/v1/users",
context="""
Follow the rest-graphql-debug skill (optional-skills/software-development/rest-graphql-debug).
Base URL: https://api.example.com
Auth: Bearer token from API_TOKEN env var.
For each verb (POST, GET, PATCH, DELETE):
- happy path: assert status + response schema
- error cases: 400, 404, 422
- log a repro curl for any failure (redact tokens)
Output: pass/fail per endpoint + correlation IDs for failures.
""",
toolsets=["terminal", "file"],
)
Output Format
When reporting findings:
## Finding
Endpoint: POST /api/v1/users
Status: 422 Unprocessable Entity
Req ID: req_abc123xyz
## Repro
curl -X POST https://api.example.com/api/v1/users \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer <REDACTED>' \
-d '{"name":"test"}'
## Root Cause
Missing required field `email`. Server validation rejects before processing.
## Fix
-d '{"name":"test","email":"test@example.com"}'
Related
systematic-debugging — once the failing API layer is isolated, root-cause your code
test-driven-development — write the regression test before shipping the fix
Common Rationalizations
Rationalization
Reality
"I'll guess the fix based on the exception text."
Guessing leads to trial-and-error changes that bloat the git history and introduce side effects. Always isolate the network layer first.
"A 200 OK status means the API request succeeded."
Many APIs (especially GraphQL) return 200 OK but include structured exceptions or partial data. Always parse and validate payload bodies.
"We don't need a formal reproducer for simple bugs."
If you can't reliably reproduce a failure, you can't verify that your change was the active fix rather than transient environmental behavior.
Red Flags
Guessing or modifying source code without replicating the bug locally or via curl first.
Blindly ignoring SSL certificate warnings (-k or NODE_TLS_REJECT_UNAUTHORIZED=0) in production code.
Swallowing internal exception messages inside catch blocks without mapping, logging, or reporting them.
Sharing full API auth headers, Bearer tokens, or passwords inside plain text logs or vendor bug reports.
Verification
After completing the debugging process, verify:
Network layer is successfully isolated and localized (Connectivity, TLS, Auth, Payload, or Application logic).
A reproducible test scenario (curl command, script, or test case) predictably fails.
The identified fix is applied and verified as passing using the same reproducer criteria.
All sensitive credentials, tokens, or PII are redacted from diagnostics, curl statements, and debug scripts.