REST API Authentication with cURL: API Keys, Bearer Tokens, and Basic Auth

The Short Version

Most REST APIs authenticate cURL requests with an API key or Bearer token. Some still use HTTP Basic authentication. Put the credential in the header or authentication option named in the API provider’s documentation.

# API key header
curl --fail-with-body https://api.example.com/v1/account \
  -H "X-API-Key: $API_KEY"

# Bearer token
curl --fail-with-body https://api.example.com/v1/account \
  -H "Authorization: Bearer $ACCESS_TOKEN"

# Basic authentication
curl --fail-with-body https://api.example.com/v1/account \
  --user "$API_USER:$API_PASSWORD"

Use the exact header name and authentication scheme the API documents. If an API expects X-API-Key, it won’t necessarily accept that same key in an Authorization header.

How the Authentication Methods Differ

MethodTypical formatCommon use
API keyX-API-Key: valueDeveloper APIs and service integrations
Bearer tokenAuthorization: Bearer tokenOAuth 2.0 and short-lived access tokens
Basic AuthAuthorization: Basic encoded-valueInternal services and simple integrations
OAuth 2.0 client credentialsToken request followed by Bearer tokenServer-to-server applications

Use every one of these methods over HTTPS. Basic authentication only Base64-encodes the username and password. It doesn’t encrypt either one.

What to Check Beforehand

  • Confirm the API base URL and resource path.
  • Find out whether the API expects a key or token, a username and password, or OAuth client credentials.
  • Don’t keep credentials in scripts committed to source control.
  • Work in a test or sandbox environment if the API offers one.

For the examples that follow, keep secrets in temporary environment variables rather than typing them into every command:

export API_KEY='replace-with-your-key'
export ACCESS_TOKEN='replace-with-your-token'

On Windows PowerShell, the equivalent commands are:

$env:API_KEY = 'replace-with-your-key'
$env:ACCESS_TOKEN = 'replace-with-your-token'

Using an API Key

APIs often define a custom header such as X-API-Key, Api-Key, or X-Auth-Token. Header names aren’t case-sensitive, but the expected name and the way its value is formatted still matter.

curl --fail-with-body --silent --show-error \
  https://api.example.com/v1/projects \
  -H "X-API-Key: $API_KEY" \
  -H "Accept: application/json"

Other services put the key in the standard Authorization header, paired with a custom scheme:

curl https://api.example.com/v1/projects \
  -H "Authorization: ApiKey $API_KEY"

Sometimes the API accepts a key in the query string instead:

curl "https://api.example.com/v1/projects?api_key=$API_KEY"

Avoid query-string credentials unless the API specifically requires them. URLs can end up in browser history and server access logs. Proxy logs and analytics systems may retain them too.

Sending a Bearer Token

OAuth 2.0 access tokens commonly use the Bearer scheme, as do a lot of personal access tokens. There must be one space between Bearer and the token.

curl --fail-with-body --silent --show-error \
  https://api.example.com/v1/profile \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Accept: application/json"

For an authenticated POST request that sends JSON, use:

curl --fail-with-body --silent --show-error \
  https://api.example.com/v1/tasks \
  -X POST \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"Review backup status"}'

Bearer tokens can expire. So if a request worked earlier and now returns 401 Unauthorized, check the token’s expiration time. You may need to refresh or regenerate it.

Using HTTP Basic Authentication

With --user, cURL builds the Basic Authorization header for you:

export API_USER='integration-user'
export API_PASSWORD='replace-with-password'

curl --fail-with-body https://api.example.com/v1/status \
  --user "$API_USER:$API_PASSWORD"

For unattended jobs, a restricted netrc file keeps expanded credentials out of the command arguments. The file looks like this:

machine api.example.com
  login integration-user
  password replace-with-password

Save it as a dedicated file, restrict who can read it, then point cURL to that file:

chmod 600 ~/.api-netrc
curl --netrc-file ~/.api-netrc https://api.example.com/v1/status

Getting an OAuth 2.0 Access Token

Server-to-server integrations often rely on the OAuth 2.0 client credentials grant. The first step is to exchange the client ID and client secret for an access token.

curl --fail-with-body --silent --show-error \
  https://auth.example.com/oauth/token \
  --user "$CLIENT_ID:$CLIENT_SECRET" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  --data-urlencode "grant_type=client_credentials" \
  --data-urlencode "scope=projects.read"

A successful response usually includes an access_token and token_type, along with an expiration value. If jq is installed, you can capture the token directly instead of copying it by hand:

ACCESS_TOKEN=$(curl --fail-with-body --silent --show-error \
  https://auth.example.com/oauth/token \
  --user "$CLIENT_ID:$CLIENT_SECRET" \
  --data-urlencode "grant_type=client_credentials" \
  | jq -r '.access_token')

Then use the returned value as a Bearer token in later API requests.

Tracking Down Authentication Errors

ResponseLikely meaningWhat to check
400 Bad RequestMalformed token requestGrant type, form encoding, and required parameters
401 UnauthorizedCredential is missing, invalid, or expiredHeader format, token value, expiration, and API hostname
403 ForbiddenIdentity is known but lacks permissionScopes, roles, resource policy, and account restrictions
429 Too Many RequestsRate limit exceededRetry headers, request frequency, and API quota

To inspect the response headers and HTTP status line, add --include:

curl --include https://api.example.com/v1/profile \
  -H "Authorization: Bearer $ACCESS_TOKEN"

The verbose option, curl -v, helps with connection troubleshooting. Be careful, though: its output may expose authentication headers. Redact every secret before you save the logs or share terminal output.

Keeping Credentials Safer

  • Use HTTPS, and make sure the hostname matches the API documentation.
  • Give the integration only the scopes or permissions it needs.
  • Choose short-lived tokens over permanent credentials where possible.
  • Rotate exposed keys right away. Removing them from a Git commit isn’t enough.
  • Don’t use --insecure to bypass certificate validation in production.
  • Keep production secrets in a secret manager or a protected CI/CD variable store.
  • Add --fail-with-body so scripts fail on HTTP errors while keeping the response body available for diagnosis.

Checking That It Worked

A successful authentication test should return the expected 2xx status and resource data. To save the body while printing only the HTTP status, run:

curl --silent --show-error \
  --output response.json \
  --write-out "%{http_code}\n" \
  https://api.example.com/v1/profile \
  -H "Authorization: Bearer $ACCESS_TOKEN"

Open response.json and check the identity carefully. The account, tenant, or project in the response should match the one tied to the credential.

Leave a Comment

Related Posts