cheat-sheets

REST APIs

Calling and testing HTTP/JSON APIs from the command line with curl and jq. Use it for homelab and service APIs (for example a UPS wrapper, Proxmox or the Claude API).

Documentation: curl manual · MDN: HTTP methods

Verification: Flags checked against the curl man page from memory; not run here. --json needs curl 7.82 or later and --fail-with-body needs 7.76 or later.

See also: curl · jq · HTTP Status Codes · apcupsd


Methods #

METHODUSESAFEIDEMPOTENT
GETRead a resource or listYesYes
HEADHeaders onlyYesYes
POSTCreate, or trigger an actionNoNo
PUTReplace a resourceNoYes
PATCHPartial updateNoNot guaranteed
DELETERemove a resourceNoYes
OPTIONSAsk what is allowed (CORS preflight)YesYes

Requests with curl #

COMMANDDESCRIPTION
curl -sS <URL>GET, quiet but show errors
curl -i <URL>Include response headers
curl -I <URL>HEAD request
curl -X DELETE <URL>Set the method explicitly
curl -H 'Accept: application/json' <URL>Add a header
curl --json '{"name":"x"}' <URL>POST JSON and set Content-Type and Accept (7.82+)
curl -X POST -H 'Content-Type: application/json' -d '{"name":"x"}' <URL>POST JSON on older curl
curl -d @body.json -H 'Content-Type: application/json' <URL>Body from a file
curl -X PUT -H 'Content-Type: application/json' -d @body.json <URL>Replace a resource
curl -X PATCH -H 'Content-Type: application/json' -d '{"a":1}' <URL>Partial update
curl -G --data-urlencode 'q=a b' <URL>Add a URL-encoded query string to a GET
curl -F file=@PATH <URL>Multipart file upload
curl -L <URL>Follow redirects
curl -o out.json <URL>Save the body
curl -K curl.confRead options from a file (keeps secrets out of shell history)

Authentication #

STYLECOMMAND
Bearer tokencurl -H "Authorization: Bearer $API_TOKEN" <URL>
Bearer token (curl option)curl --oauth2-bearer "$API_TOKEN" <URL>
Basic authcurl -u <USER> <URL> (curl prompts for the password; or use --netrc)
API key in headercurl -H "X-API-Key: $API_KEY" <URL>
API key in querycurl "<URL>?api_key=$API_KEY" (leaks into logs; avoid)
Client certificatecurl --cert client.pem --key client.key <URL>
Custom CAcurl --cacert ca.pem <URL>
Skip TLS verificationcurl -k <URL> (testing only; never in scripts)

Keep tokens in environment variables or a -K config file, not in the command line, and rotate any that reach shell history or logs.


Reading Responses #

COMMANDDESCRIPTION
curl -s <URL> | jq .Pretty-print JSON
curl -s <URL> | jq -r '.items[].name'Extract fields as raw text
curl -s <URL> | jq 'map(select(.status=="ok"))'Filter an array
curl -s -o /dev/null -w '%{http_code}\n' <URL>Print only the status code
curl -s -o /dev/null -w '%{time_total}\n' <URL>Request time in seconds
curl -fsS <URL>Exit non-zero on HTTP 4xx/5xx, still show errors
curl --fail-with-body -sS <URL>Same, but print the error body (7.76+)
curl -s -D - -o /dev/null <URL>Print headers only

Reliability #

OPTIONMEANING
--connect-timeout 5Seconds allowed to connect
-m 30 / --max-time 30Total time limit
--retry 3Retry transient failures
--retry-delay 2Seconds between retries
--retry-all-errorsRetry on any error, not just transient ones (7.71+; only for idempotent calls)

Retry only idempotent requests (GET, PUT, DELETE). A retried POST can create duplicates unless the API supports an idempotency key.


Pagination #

PATTERNEXAMPLE
Page and size?page=2&per_page=100
Offset and limit?offset=200&limit=100
Cursor?cursor=<NEXT_CURSOR> taken from the previous response
Link headerLink: <URL>; rel="next": follow next until absent

Loop on a cursor:

bash
cursor=""
while :; do
  resp=$(curl -fsS -H "Authorization: Bearer $API_TOKEN" "https://api.example.com/items?cursor=$cursor")
  echo "$resp" | jq -c '.items[]'
  cursor=$(echo "$resp" | jq -r '.next_cursor // empty')
  [ -n "$cursor" ] || break
done

Status Codes #

RANGEMEANINGACT
2xxSuccess (200, 201 created, 204 no content)Use the result
3xxRedirectAdd -L, check the new URL
400 / 422Bad request or invalid bodyFix the payload
401Not authenticatedCheck the token or header
403Authenticated but not allowedCheck scopes and permissions
404Not foundCheck the path and ID
409ConflictRe-read the resource, then retry
429Rate limitedHonour Retry-After, back off
5xxServer errorRetry with back-off if idempotent

Full list: HTTP Status Codes.


Design Conventions #

  • Nouns in paths, plural collections: /users, /users/42.
  • Method carries the verb; avoid /getUser.
  • Version the API: /v1/... or an Accept header.
  • Return the right status code and a JSON error body with a message.
  • Support filtering, sorting and pagination on list endpoints.
  • Use HTTPS and short-lived tokens.

Troubleshooting #

SYMPTOMCHECK
401 UnauthorizedHeader name and the Bearer prefix (with a space after it); token expired
415 Unsupported Media TypeAdd -H 'Content-Type: application/json'
400 on a valid-looking bodyShell quoting; send from a file with -d @body.json
Empty output, exit code 0-s hides errors; use -sS, and -i to see the status
curl: (60) certificate errorWrong CA or self-signed cert: use --cacert, not -k
curl: (7) connection refusedService down, wrong port or firewall
Response not JSONRedirect to a login page: add -i and check Location
jq: parse errorBody is HTML or empty; inspect it before piping to jq
Search all sheets

Tip: / or Ctrl/Cmd+K to open, Esc to close.