API keys let cURL call protected endpoints from shells, cron jobs, CI runners, and smoke-test scripts without a browser login or a full OAuth exchange. That keeps API reachability and authorization checks easy to reproduce when a service expects one reusable secret on every request.

cURL does not have a dedicated API-key mode. It sends whatever field the API contract expects, which is usually a custom header such as X-API-Key or an Authorization: ApiKey … header, and the same request can be repeated from a restricted cURL config file with --config when local reuse matters.

API keys are still long-lived secrets, so transport and storage need the same care as passwords. Use HTTPS, keep local secret files readable only by the current account, avoid query-string auth unless the API explicitly requires it, and use --disable during troubleshooting when a local .curlrc could silently change headers or output. The examples below use masked provider-style key values and placeholder project identifiers so the request and response shape stays realistic without exposing a reusable secret.

Steps to authenticate with an API key in cURL:

  1. Save the key in a file that only the current user can read before building requests around it.
    $ install -d -m 700 ~/.config/curl
    $ printf '%s\n' 'api_live_9f47c2b1...7291b5c8' > ~/.config/curl/api.key
    $ chmod 600 ~/.config/curl/api.key

    A plain-text key in a world-readable file or shared repository is enough for anyone with access to replay API calls until the key is rotated.

  2. Load the key into the current shell so later commands reuse one value consistently.
    $ API_KEY="$(tr -d '\n' < ~/.config/curl/api.key)"
    $ printf '%s-byte key loaded\n' "${#API_KEY}"
    28-byte key loaded

    Removing the trailing newline keeps the header value identical to what the API expects.

  3. Send the key in the provider's dedicated header when the API expects a field such as X-API-Key.
    $ curl --disable --silent --show-error \
      --header "X-API-Key: ${API_KEY}" \
      --write-out "\nHTTP %{http_code}\n" \
      https://api.example.net/v1/projects
    {
      "authenticated": true,
      "method": "x-api-key",
      "project": "billing-core"
    }
    HTTP 200

    Starting with --disable keeps local .curlrc settings from adding unrelated defaults while debugging the exact request shape.

  4. Switch to the documented Authorization scheme when the provider places the key there instead of in a custom field.
    $ curl --disable --silent --show-error \
      --header "Authorization: ApiKey ${API_KEY}" \
      --write-out "\nHTTP %{http_code}\n" \
      https://api.example.net/v1/projects
    {
      "authenticated": true,
      "method": "authorization",
      "project": "billing-core"
    }
    HTTP 200

    Copy the scheme prefix exactly as documented because ApiKey, Token, vendor labels, and spacing rules are not interchangeable.

  5. Inspect the outbound request with verbose output when the server rejects a key that appears valid.
    $ curl --disable --silent --show-error --verbose \
      --header "X-API-Key: ${API_KEY}" \
      https://api.example.net/v1/projects 2>&1 | sed -n '1,12p'
    *   Trying 203.0.113.10:443...
    * Connected to api.example.net (203.0.113.10) port 443
    > GET /v1/projects HTTP/1.1
    > Host: api.example.net
    > User-Agent: curl/8.7.1
    > Accept: */*
    > X-API-Key: api_live_9f47c2b1...7291b5c8
    >
    * Request completely sent off

    The example above masks the key for publication, but live verbose output includes the full secret value, so keep it out of shared logs and incident tickets unless it is redacted first.

  6. Create a temporary cURL config file when repeated local calls should reuse the same header without rewriting the command line.
    $ printf 'header = "X-API-Key: %s"\n' "${API_KEY}" > .curl-api-key
    $ chmod 600 .curl-api-key
    $ curl --disable --silent --show-error \
      --config .curl-api-key \
      --write-out "\nHTTP %{http_code}\n" \
      https://api.example.net/v1/projects
    {
      "authenticated": true,
      "method": "x-api-key",
      "project": "billing-core"
    }
    HTTP 200

    A short-lived config file is useful for repeated local tests, but it should stay outside version control and inherit restrictive permissions.

  7. Use a query parameter only when the API explicitly documents URL-based key authentication.
    $ curl --disable --silent --show-error \
      --url-query "api_key=${API_KEY}" \
      --write-out "\nHTTP %{http_code}\n" \
      https://api.example.net/v1/projects
    {
      "authenticated": true,
      "method": "query",
      "project": "billing-core"
    }
    HTTP 200

    Query parameters are commonly copied into browser history, proxy logs, load-balancer logs, and monitoring traces, so they are usually the least private way to send an API key.

  8. Test the rejection path with a known-bad key so authentication failures are not confused with DNS, TLS, or upstream transport issues.
    $ curl --disable --silent --show-error \
      --header "X-API-Key: api_revoked_2f81c4d7...5c9ab132" \
      --write-out "\nHTTP %{http_code}\n" \
      https://api.example.net/v1/projects
    {
      "authenticated": false,
      "error": "invalid_api_key"
    }
    HTTP 401

    A 401 or 403 with an authentication payload shows that the server received the request and refused the credential instead of failing before the API layer.

  9. Clear the loaded key and any temporary config file after the request run is complete.
    $ unset API_KEY
    $ rm -f .curl-api-key

    Removing the variable only cleans up the current shell, so any copied logs, response captures, or saved secret files still need separate handling.