| name | api-dev |
| description | Scaffold, test, document, and debug REST and GraphQL APIs. Use when the user needs to create API endpoints, write integration tests, generate OpenAPI specs, test with curl, mock APIs, or troubleshoot HTTP issues. |
| metadata | {"clawdbot":{"emoji":"🔌","requires":{"anyBins":["curl","node","python3"]},"os":["linux","darwin","win32"]}} |
API Development
Build, test, document, and debug HTTP APIs from the command line. Covers the full API lifecycle: scaffolding endpoints, testing with curl, generating OpenAPI docs, mocking services, and debugging.
When to Use
- Scaffolding new REST or GraphQL endpoints
- Testing APIs with curl or scripts
- Generating or validating OpenAPI/Swagger specs
- Mocking external APIs for development
- Debugging HTTP request/response issues
- Load testing endpoints
Testing APIs with curl
GET requests
curl -s https://api.example.com/users | jq .
curl -s -H "Authorization: Bearer $TOKEN" \
-H "Accept: application/json" \
https://api.example.com/users | jq .
curl -s "https://api.example.com/users?page=2&limit=10" | jq .
curl -si https://api.example.com/users
POST/PUT/PATCH/DELETE
curl -s -X POST https://api.example.com/users \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{"name": "Alice", "email": "alice@example.com"}' | jq .
curl -s -X PUT https://api.example.com/users/123 \
-H "Content-Type: application/json" \
-d '{"name": "Alice Updated", "email": "alice@example.com"}' | jq .
curl -s -X PATCH https://api.example.com/users/123 \
-H "Content-Type: application/json" \
-d '{"name": "Alice V2"}' | jq .
curl -s -X DELETE https://api.example.com/users/123
curl -s -X POST https://api.example.com/upload \
-F "file=@document.pdf" \
-F "description=My document"
Debug requests
curl -v https://api.example.com/health 2>&1
curl -sI https://api.example.com/health
curl -s -o /dev/null -w "DNS: %{time_namelookup}s\nConnect: %{time_connect}s\nTLS: %{time_appconnect}s\nFirst byte: %{time_starttransfer}s\nTotal: %{time_total}s\n" https://api.example.com/health
curl -sL https://api.example.com/old-endpoint
curl -s -o response.json https://api.example.com/data
API Test Scripts
Bash test runner
#!/bin/bash
BASE_URL="${1:-http://localhost:3000}"
PASS=0
FAIL=0
assert_status() {
local method="$1" url="$2" expected="$3" body="$4"
local args=(-s -o /dev/null -w "%{http_code}" -X "$method")
if [ -n "$body" ]; then
args+=(-H "Content-Type: application/json" -d "$body")
fi
local status
status=$(curl "${args[@]}" "$BASE_URL$url")
if [ "$status" = "$expected" ]; then
echo "PASS: $method $url -> $status"
((PASS++))
else
echo "FAIL: $method $url -> $status (expected $expected)"
((FAIL++))
fi
}
assert_json() {
local url="" jq_expr= expected=
actual
actual=$(curl -s | jq -r )
[ = ];
((PASS++))
((FAIL++))
}
assert_status GET /health 200
assert_status POST /api/users 201
assert_status GET /api/users 200
assert_json /api/users
assert_status DELETE /api/users/1 204
assert_status GET /api/admin 401
assert_status GET /api/admin 403
[ -eq 0 ] && 0 || 1
Python test runner
"""api_test.py - API integration test suite."""
import json, sys, urllib.request, urllib.error
BASE = sys.argv[1] if len(sys.argv) > 1 else "http://localhost:3000"
PASS = FAIL = 0
def request(method, path, body=None, headers=None):
"""Make an HTTP request, return (status, body_dict, headers)."""
url = f"{BASE}{path}"
data = json.dumps(body).encode() if body else None
hdrs = {"Content-Type": "application/json", "Accept": "application/json"}
if headers:
hdrs.update(headers)
req = urllib.request.Request(url, data=data, headers=hdrs, method=method)
try:
resp = urllib.request.urlopen(req)
body = json.loads(resp.read().decode()) if resp.read() else None
except urllib.error.HTTPError as e:
return e.code, None, dict(e.headers)
return resp.status, body, dict(resp.headers)
def test(name, fn):
"""Run a test function, track pass/fail."""
global PASS, FAIL
try:
fn()
print(f" PASS: ")
PASS +=
AssertionError e:
()
FAIL +=
():
actual == expected,
()
test(, : (
assert_eq(request(, )[], )
))
test(, : (
assert_eq(request(, , {: , : })[], )
))
test(, : (
assert_eq((request(, )[]), )
))
test(, : (
assert_eq(request(, )[], )
))
()
sys.exit( FAIL == )
OpenAPI Spec Generation
Generate from existing endpoints
cat > openapi.yaml << 'EOF'
openapi: "3.0.3"
info:
title: My API
version: "1.0.0"
description: API description here
servers:
- url: http://localhost:3000
description: Local development
paths:
/health:
get:
summary: Health check
responses:
"200":
description: Service is healthy
content:
application/json:
schema:
type: object
properties:
status:
type: string
example: ok
/api/users:
get:
summary: List users
parameters:
- name: page
in: query
schema:
type: integer
default: 1
- name: limit
in: query
schema:
type: integer
default: 20
responses:
"200":
description: List of users
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/User"
post:
summary: Create user
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/CreateUser"
responses:
"201":
description: User created
content:
application/json:
schema:
$ref: "#/components/schemas/User"
"400":
description: Validation error
/api/users/{id}:
get:
summary: Get user by ID
parameters:
- name: id
: path
required:
schema:
: string
responses:
:
description: User details
content:
application/json:
schema:
:
:
description: Not found
components:
schemas:
User:
: object
properties:
:
: string
name:
: string
email:
: string
format: email
createdAt:
: string
format: date-time
CreateUser:
: object
required:
- name
- email
properties:
name:
: string
email:
: string
format: email
securitySchemes:
bearerAuth:
: http
scheme: bearer
bearerFormat: JWT
EOF
Validate OpenAPI spec
npx @redocly/cli lint openapi.yaml
python3 -c "import yaml; yaml.safe_load(open('openapi.yaml'))" && echo "Valid YAML"
Mock Server
Quick mock with Python
"""mock_server.py - Lightweight API mock from OpenAPI-like config."""
import json, http.server, re, sys
PORT = int(sys.argv[1]) if len(sys.argv) > 1 else 8080
ROUTES = {
("GET", "/health"): {"status": 200, "body": {"status": "ok"}},
("GET", "/api/users"): {"status": 200, "body": [
{"id": "1", "name": "Alice", "email": "alice@example.com"},
{"id": "2", "name": "Bob", "email": "bob@example.com"},
]},
("POST", "/api/users"): {"status": 201, "body": {"id": "3", "name": "Created"}},
("GET", r"/api/users/\w+"): {"status": 200, "body": {"id": "1", "name": "Alice"}},
(, ): {: , : },
}
(http.server.BaseHTTPRequestHandler):
():
(method, pattern), response ROUTES.items():
.command == method re.fullmatch(pattern, .path.split()[]):
.send_response(response[])
response[] :
.send_header(, )
.end_headers()
.wfile.write(json.dumps(response[]).encode())
:
.end_headers()
.send_response()
.send_header(, )
.end_headers()
.wfile.write(json.dumps({: }).encode())
do_GET = do_POST = do_PUT = do_PATCH = do_DELETE = _handle
():
()
()
http.server.HTTPServer((, PORT), MockHandler).serve_forever()
Run: python3 mock_server.py 8080
Node.js Express Scaffolding
Minimal REST API
const express = require('express');
const app = express();
app.use(express.json());
const items = new Map();
let nextId = 1;
app.get('/api/items', (req, res) => {
const { page = 1, limit = 20 } = req.query;
const all = [...items.values()];
const start = (page - 1) * limit;
res.json({ items: all.slice(start, start + +limit), total: all.length });
});
app.get('/api/items/:id', (req, res) => {
const item = items.get(req.params.id);
if (!item) return res.status(404).json({ error: 'Not found' });
res.json(item);
});
app.post('/api/items', () => {
{ name, description } = req.;
(!name) res.().({ : });
id = (nextId++);
item = { id, name, : description || , : ().() };
items.(id, item);
res.().(item);
});
app.(, {
(!items.(req..)) res.().({ : });
item = { ...req., : req.., : ().() };
items.(req.., item);
res.(item);
});
app.(, {
(!items.(req..)) res.().({ : });
items.(req..);
res.().();
});
app.( {
.(err.);
res.().({ : });
});
= process.. || ;
app.(, .());
Setup
mkdir my-api && cd my-api
npm init -y
npm install express
node server.js
Debugging Patterns
Check if port is in use
lsof -i :3000
ss -tlnp | grep 3000
kill $(lsof -t -i :3000)
Test CORS
curl -s -X OPTIONS https://api.example.com/users \
-H "Origin: http://localhost:3000" \
-H "Access-Control-Request-Method: POST" \
-H "Access-Control-Request-Headers: Content-Type" \
-I
Watch for response time regressions
for i in $(seq 1 10); do
curl -s -o /dev/null -w "%{time_total}\n" http://localhost:3000/api/users
done | awk '{sum+=$1; if($1>max)max=$1} END {printf "Avg: %.3fs, Max: %.3fs\n", sum/NR, max}'
Inspect JWT tokens
echo "$TOKEN" | cut -d. -f2 | base64 -d 2>/dev/null | jq .
Tips
- Use
jq for JSON response processing: curl -s url | jq '.items[] | {id, name}'
- Set
Content-Type header on every request with a body - missing it causes silent 400s
- Use
-w '\n' with curl to ensure output ends with a newline
- For large response bodies, pipe to
jq -C . | less -R for colored paging
- Test error paths: invalid JSON, missing fields, wrong types, unauthorized, not found
- For WebSocket testing:
npx wscat -c ws://localhost:3000/ws