Skip to content
Latchkey

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

Terminal
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

FlagWhat it does
-H 'Content-Type: application/json'Tell the server the body is JSON
-d / --dataSend the JSON body (or @file)
-f / --failFail the step on HTTP >= 400
-w '%{http_code}'Print the status code for branching
--jsonOne-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.

Related guides

Run this faster and cheaper on Latchkey managed runners. Start free →