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
| Method | Typical format | Common use |
|---|---|---|
| API key | X-API-Key: value | Developer APIs and service integrations |
| Bearer token | Authorization: Bearer token | OAuth 2.0 and short-lived access tokens |
| Basic Auth | Authorization: Basic encoded-value | Internal services and simple integrations |
| OAuth 2.0 client credentials | Token request followed by Bearer token | Server-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-passwordSave 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/statusGetting 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
| Response | Likely meaning | What to check |
|---|---|---|
400 Bad Request | Malformed token request | Grant type, form encoding, and required parameters |
401 Unauthorized | Credential is missing, invalid, or expired | Header format, token value, expiration, and API hostname |
403 Forbidden | Identity is known but lacks permission | Scopes, roles, resource policy, and account restrictions |
429 Too Many Requests | Rate limit exceeded | Retry 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
--insecureto bypass certificate validation in production. - Keep production secrets in a secret manager or a protected CI/CD variable store.
- Add
--fail-with-bodyso 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.
