cURLParse cURL Converter

Example: Authenticated JSON POST (GitHub API)

cURLParse Guides · Updated 2026-10-18 · All guides
curl -X POST https://api.github.com/repos/octocat/hello-world/issues \
  -H 'Authorization: Bearer ***' \
  -H 'Accept: application/vnd.github.v3+json' \
  -H 'Content-Type: application/json' \
  -d '{"title":"Weekly sync","body":"agenda TBD"}'
  • Accept here selects an API version, not a media type — GitHub's v3 header is required for stable JSON.
  • No --compressed? GitHub sends one anyway; the converter injects it only when the source had it, so output stays honest.
  • In Python: requests.post(url, headers={...}, json={"title": ...}) — never data="{\"title\"...}" stringly JSON; that's the bug from this guide.

Reading the response

A created issue comes back with a Location header and a body whose url is the canonical address of the new resource:

curl -sS -D - -o /tmp/issue.json -w '%{http_code}\n' \
  https://api.github.com/repos/octocat/hello-world/issues \
  -H 'Authorization: Bearer ***' \
  -H 'Accept: application/vnd.github.v3+json' \
  -H 'Content-Type: application/json' \
  -d '{"title":"Weekly sync","body":"agenda TBD"}'
  • 201 Created plus Location means the resource exists. 200 OK on this endpoint means something else answered — a proxy, a cached GET, or a redirect you didn't follow. Check -w '%{url_effective}\n' with -L to see where you actually landed.
  • 401 means the token is missing, expired, or was revoked by GitHub because it leaked (GitHub revokes tokens pushed in public repos — including ones pasted into a public gist). 403 with insufficient scope in the body means the token exists but lacks repo; the body is the diagnosis, so print it: --fail-with-body keeps it and still exits non-zero.
  • 422 with resource: Issue almost always means the repo doesn't exist or you renamed it — GitHub answers 422 for missing repos on authenticated requests to hide private repos from unauthenticated probes.

Idempotency: this POST has none

Re-run the command and you get a second issue. curl gives you no dedupe hook here, so wrap it:

# check-then-create in one shell function; still racy, but stops the double-paste
issue_exists() {
  curl -sS -o /dev/null -w '%{http_code}' \
    "https://api.github.com/search/issues?q=repo:octocat/hello-world+in:title+%22Weekly+sync%22" \
    -H "Authorization: Bearer $GITHUB_TOKEN"
}

For anything that must not double-fire, keep the create behind a queue with at-most-once delivery, not behind a retried curl. And note what a retried curl does to this request through a redirect — see the redirect trap in curl works, my code fails: a 301 in front of this POST sends an empty POST that can 200 while creating nothing.

Storing the token

$GITHUB_TOKEN in the environment, referenced as -H "Authorization: Bearer $GITHUB_TOKEN" with double quotes. A single-quoted header sends the literal string $GITHUB_TOKEN and GitHub answers 401 Bad credentials — the most common failure of this exact command. The quoting guide covers why the shell, not curl, is at fault.

FAQ

Why does GitHub want Accept: application/vnd.github.v3+json instead of application/json? The vendor media type pins the API representation version. Plain application/json gets you the default version, which can shift under you.

Should the bearer token ever be in the URL? No. Query-string tokens are logged at every intermediary; header tokens aren't. See GET with a body for the same logging argument.

Developer Sponsor / Partner
Copied to clipboard!