REST API Patterns
RESTful API design patterns, status codes, and request construction.
HTTP Methods
# List collection with filters
curl -s https://api.example.com/v1/devices?status=active&limit=50 | jq .
# Read single resource by ID
curl -s https://api.example.com/v1/devices/42 | jq .
curl -s -X POST https://api.example.com/v1/devices \
-H "Content-Type: application/json" \
-d '{"name": "sw-core-01", "type": "switch", "vlan": 10}' | jq .
# Returns 201 Created with Location header
curl -s -X PUT https://api.example.com/v1/devices/42 \
-H "Content-Type: application/json" \
-d '{"name": "sw-core-01", "type": "switch", "vlan": 20, "status": "active"}' | jq .
curl -s -X PATCH https://api.example.com/v1/devices/42 \
-H "Content-Type: application/json" \
-d '{"vlan": 30}' | jq .
curl -s -X DELETE https://api.example.com/v1/devices/42
# 204 No Content on success
Status Codes
Success (2xx)
| Code | Meaning |
|---|---|
|
GET, PUT, PATCH success with response body |
|
POST success, new resource created, check Location header |
|
DELETE success, no response body |
Redirection (3xx)
| Code | Meaning |
|---|---|
|
Resource relocated, update bookmarks |
|
Client cache is valid, no body returned |
Client Error (4xx)
| Code | Meaning |
|---|---|
|
Malformed syntax — missing field, wrong type |
|
Missing or invalid credentials (really "unauthenticated") |
|
Valid credentials but insufficient permissions |
|
Resource does not exist |
|
State conflict — duplicate creation, version mismatch |
|
Valid syntax but semantic error — validation failure |
|
Rate limit exceeded, check |
Server Error (5xx)
| Code | Meaning |
|---|---|
|
Unhandled exception on server |
|
Upstream service failed |
|
Overloaded or maintenance, check |
Headers
curl -s https://api.example.com/v1/devices \
-H "Content-Type: application/json" \ # body format
-H "Accept: application/json" \ # desired response format
-H "Authorization: Bearer $TOKEN" \ # authentication
-H "X-Request-ID: $(uuidgen)" # distributed tracing
# First request -- server returns ETag
etag=$(curl -sI https://api.example.com/v1/devices/42 | grep -i etag | awk '{print $2}' | tr -d '\r')
# Subsequent request -- 304 if unchanged, saves bandwidth
curl -s -H "If-None-Match: $etag" https://api.example.com/v1/devices/42
# Include ETag from GET in the PUT/PATCH
curl -s -X PUT https://api.example.com/v1/devices/42 \
-H "If-Match: \"$etag\"" \
-H "Content-Type: application/json" \
-d '{"name": "updated-name", "vlan": 20}'
# 412 Precondition Failed if resource changed since read
curl -s -X POST https://api.example.com/v1/orders \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{"item": "firewall-license", "quantity": 1}'
Query Parameters
# Filter
curl -s "https://api.example.com/v1/devices?type=switch&status=active"
# Sort (- prefix = descending)
curl -s "https://api.example.com/v1/devices?sort=-created_at,name"
# Sparse fieldsets -- only requested fields
curl -s "https://api.example.com/v1/devices?fields=id,name,status"
# Resource expansion -- embed related objects, avoid N+1
curl -s "https://api.example.com/v1/devices?include=interfaces,vlans"
HATEOAS
{
"id": 42,
"name": "sw-core-01",
"_links": {
"self": "/v1/devices/42",
"interfaces": "/v1/devices/42/interfaces",
"vlans": "/v1/devices/42/vlans",
"collection": "/v1/devices"
}
}