curl POST JSON: The Standard CI Pattern
Posting JSON correctly means the right header, a failing exit code, and a captured status.
This is the everyday write request in pipelines: send JSON, fail loudly on errors, and know what status you got back.
What it does
A JSON POST sends a body with Content-Type: application/json. curl does not set that header for you with -d, so you add it explicitly or use --json. Pairing the request with -f / --fail and -w makes it CI-safe: the step fails on a bad status and you can record the code.
Common usage
curl -fsS \
-X POST \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $TOKEN" \
-d '{"ref":"main","environment":"prod"}' \
https://api.example.com/deployments
# capture status and body separately
code=$(curl -sS -o resp.json -w '%{http_code}' \
-H 'Content-Type: application/json' -d @body.json https://api.example.com/x)Flags
| Flag | What it does |
|---|---|
| -H 'Content-Type: application/json' | Tell the server the body is JSON |
| -d / --data | Send the JSON body (or @file) |
| -f / --fail | Fail the step on HTTP >= 400 |
| -w '%{http_code}' | Print the status code for branching |
| --json | One-flag alternative on curl 7.82+ |
In CI
Quote the JSON in single quotes so the shell does not expand it, and inject secrets via environment variables, not literals. Use -fsS so a 4xx/5xx fails the step. To branch on the result, capture %{http_code} with -w and write a flag to $GITHUB_OUTPUT.
Common errors in CI
A 400 with "invalid JSON" usually means the shell mangled quotes; switch to -d @file.json. A 415 means Content-Type was missing. curl: (22) ... error: 401 means the Authorization header is wrong or the token expired.