Claude API auth: key, Bedrock, Vertex, Foundry
Four ways to authenticate a Claude API client on a VPS: an Anthropic key, AWS IAM on Bedrock, Google ADC on Vertex, Entra on Foundry, plus safe storage.
The four Claude API authentication routes
Claude API authentication comes down to one decision: which credential your client puts on the wire. There are four answers, and they are not variants of one mechanism. The direct Anthropic API sends a static key in an x-api-key header. Amazon Bedrock signs every request with AWS credentials, and no Anthropic key exists anywhere in that setup. Google Cloud sends a short-lived Google access token. Microsoft Foundry takes an Azure-issued key or a Microsoft Entra token.
This guide is for wiring an SDK (software development kit) into a service running on a Linux server. If you are configuring the Claude Code command line tool instead, the variables and the flow are different: see pointing Claude Code at Bedrock or Vertex. If the service does not exist yet, build it first with a first Claude API app on a VPS and come back here for the credential.
Everything below was checked against Anthropic's platform documentation in August 2026. Model identifiers, prices, SDK versions and endpoint shapes all move, so this guide links the provider pages for those instead of printing values that go stale.
Route 1: an Anthropic API key
This is the direct path, and the only one where Anthropic issues the secret. Requests go to the Messages endpoint on Anthropic's API host, and every request carries three headers.
curl https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
-d '{"model": "MODEL_ID", "max_tokens": 64, "messages": [{"role": "user", "content": "Hello"}]}'Replace MODEL_ID with a current identifier from Anthropic's models overview. A healthy response is JSON holding a content array and a usage object. A wrong or expired key returns HTTP 401 with authentication_error. A missing anthropic-version header is a separate failure, because that header is required on every request; the SDKs set it for you.
Client construction is the shortest of the four, because there is nothing to construct. Every official SDK reads ANTHROPIC_API_KEY from the environment on its own.
import os
from anthropic import Anthropic
client = Anthropic() # reads ANTHROPIC_API_KEY from the environment
message = client.messages.create(
model=os.environ["CLAUDE_MODEL"],
max_tokens=64,
messages=[{"role": "user", "content": "Hello"}],
)
print(message.usage)Keeping the model identifier in the environment beside the key is worth doing. Model names change on a schedule you do not control, and redeploying code to edit one string is avoidable work.
Keys are created in the Console, where you choose an expiry at creation time: presets of 3 hours, 1 day, 7 days or 30 days, a custom duration, or Never. Expiry is fixed at creation and cannot be changed later. Anthropic emails the key's creator before a long-lived key expires, but a key with a short lifetime expires with no warning mail at all. An expired key returns 401 and cannot be reactivated, so the fix is always a new key.
There is no region to choose on the direct API, and the bill goes straight to your Anthropic organization. Workspaces scope a key to one project, which is the cleanest way to see what a single service spends. For the arithmetic behind that bill, see how per-token API pricing compares against a subscription.
One more option belongs here, because it removes the static secret entirely. Workload Identity Federation lets a workload trade an OpenID Connect (OIDC) token from an identity provider you already trust for a short-lived Anthropic token at POST /v1/oauth/token, and the SDK refreshes that token before it expires. No sk-ant-api... string is ever minted or copied anywhere. It fits Kubernetes, GitHub Actions and cloud VMs, which already carry a platform identity. A plain VPS usually has no such issuer, so on that box an API key in a file is the honest answer, and the rest of this guide treats it that way.
Route 2: AWS credentials on Amazon Bedrock
On Bedrock you hold no Anthropic key at all. The SDK signs each HTTP request with AWS Signature Version 4 (SigV4) using ordinary AWS credentials, and AWS decides whether that caller may invoke the model.
pip install -U "anthropic[bedrock]"
aws sts get-caller-identityaws sts get-caller-identity prints the account number and the ARN (Amazon Resource Name) of whatever identity your credentials resolve to. Run it before anything else. If it fails, the Claude call will fail too, because the SDK walks the same chain: constructor arguments first, then the AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN and AWS_REGION environment variables, then the AWS config file and the rest of the standard chain (SSO, assumed roles, the ECS task role, the instance metadata service).
What changes in client construction is the class and one argument.
from anthropic import AnthropicBedrock
client = AnthropicBedrock(aws_region="us-east-1")Region stops being decoration here. Bedrock endpoints are per region, model access is granted per region in the AWS console, and the region is part of the SigV4 signature, so a signature computed for one region is rejected by another. Set AWS_REGION explicitly in the service environment. Anthropic documents that the AnthropicBedrock client reads AWS_REGION and falls back to us-east-1 when it is unset, and that it does not read ~/.aws/config for the region. That is why the AWS CLI can list Claude models successfully on the same box where your Python process fails: the CLI read your config file and the client did not.
On an EC2 instance you attach an IAM (identity and access management) role and no secret ever lands on disk, because the instance metadata service hands the SDK temporary credentials. A VPS outside AWS has neither an instance role nor a metadata service. You are then choosing between an IAM user's long-lived access key pair sitting on the box, which is the same class of secret as an Anthropic key, and federation: authenticate against your identity provider, call AWS STS (security token service), and use the temporary credentials it returns. Bedrock also accepts a bearer token through AWS_BEARER_TOKEN_BEDROCK, documented with a 12 hour ceiling and described by AWS as the least preferred path.
The bill lands on your AWS account rather than with Anthropic, which is usually the whole reason to be here. Regional endpoints carry a 10% premium over the global endpoint, as documented in August 2026. One Bedrock error is worth recognising because it looks like a permissions problem and is not: Invocation of model ID ... with on-demand throughput isn't supported. Retry your request with the ID or ARN of an inference profile that contains this model. That is model routing, and no credential change will fix it.
Route 3: Google credentials on Vertex AI
Google Cloud uses Application Default Credentials (ADC), a fixed search order the Google auth libraries follow to find a credential without you naming one. ADC checks GOOGLE_APPLICATION_CREDENTIALS first, then the file written by gcloud auth application-default login, then the service account attached through the metadata server.
pip install -U "anthropic[vertex]"
gcloud auth application-default loginOn a workstation that login writes $HOME/.config/gcloud/application_default_credentials.json and you are finished. On a server it is the wrong tool, because the credential it stores belongs to a human being and dies with that person's account. Outside Google Cloud there is no metadata server either, so ADC falls through to GOOGLE_APPLICATION_CREDENTIALS pointing at a service account key file. That JSON file is a long-lived secret and needs exactly the handling described later in this guide. Inside Google Cloud, attach a service account to the VM and there is no file to protect.
from anthropic import AnthropicVertex
client = AnthropicVertex(project_id="my-project", region="global")Two things change if you drop below the SDK to raw HTTP. The model identifier moves out of the request body and into the URL path, and anthropic_version moves out of the header and into the body, where it must read vertex-2023-10-16. The credential is an ordinary Google access token.
curl https://aiplatform.googleapis.com/v1/projects/${PROJECT_ID}/locations/global/publishers/anthropic/models/${MODEL_ID}:rawPredict \
-H "Authorization: Bearer $(gcloud auth print-access-token)" \
-H "Content-Type: application/json" \
-d '{"anthropic_version": "vertex-2023-10-16", "max_tokens": 64, "messages": [{"role": "user", "content": "Hello"}]}'Region is a first-class argument. global routes dynamically for availability, us and eu are multi-region identifiers, and a name such as us-east5 pins a single region. Multi-region and regional endpoints cost 10% more than global, as documented in August 2026. Billing runs through the Google Cloud project, so quota and invoices are Google's.
Route 4: Microsoft Foundry is the Azure route
If you searched for Claude on Azure, this is the section you wanted, and a supported route does exist. Claude runs in Microsoft Foundry (formerly Azure AI Foundry), billed through the Azure Marketplace in Claude Consumption Units. You create a Foundry resource, deploy a Claude model inside it, and call an Azure-hosted endpoint at https://{resource}.services.ai.azure.com/anthropic/v1/*.
Two credentials work. The first is an Azure-issued key from the deployment's Details tab in the Foundry portal, sent in an api-key or x-api-key header. The second is a Microsoft Entra token, which is the better choice on a server because Azure role-based access control then governs who may call the endpoint.
ACCESS_TOKEN=$(az account get-access-token --resource https://ai.azure.com --query accessToken -o tsv)
curl https://${RESOURCE}.services.ai.azure.com/anthropic/v1/messages \
-H "content-type: application/json" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "anthropic-version: 2023-06-01" \
-d '{"model": "DEPLOYMENT_NAME", "max_tokens": 64, "messages": [{"role": "user", "content": "Hello"}]}'The model field carries your deployment name, not a model identifier. The two match by default, and they stop matching the moment you name a deployment yourself, which is the usual cause of a Deployment not found error on an otherwise correct request. The Python and TypeScript SDKs read ANTHROPIC_FOUNDRY_API_KEY and ANTHROPIC_FOUNDRY_RESOURCE from the environment. Foundry support is not in every SDK: as documented in August 2026 it covers C#, Java, PHP, Python and TypeScript, while the Go and Ruby SDKs need the generic client pointed at the Foundry base URL.
That workaround has a sharp edge. If ANTHROPIC_API_KEY is still set in the environment, the generic client picks it up and sends your Anthropic key to a Microsoft endpoint. Unset the variable, or disable environment defaults on the client. Entra tokens expire after about an hour, so a long-running process has to refresh them rather than capture one at start.
How long does the credential on your server live?
The data behind this chart
[
{
"label": "Anthropic key, 30-day preset",
"max_lifetime_hours": 720
},
{
"label": "Anthropic key, 7-day preset",
"max_lifetime_hours": 168
},
{
"label": "AWS STS assumed role",
"max_lifetime_hours": 12
},
{
"label": "Bedrock bearer token",
"max_lifetime_hours": 12
},
{
"label": "Entra ID access token",
"max_lifetime_hours": 1
},
{
"label": "Federated Anthropic token",
"max_lifetime_hours": 1
}
]These are ceilings and defaults published by each provider and read in August 2026, not measured figures. They matter for one reason: they tell you how long a leaked credential keeps working while you are still finding out that it leaked. The short-lived tokens at the bottom of the chart last 1 hour each, and the SDK refreshes them, so the short life costs you nothing to operate. An assumed role sits at 12 hours. A key created with the 30-day preset stays valid for 720 hours, and that is the credential which sits in a file on your server for a month.
Where the credential lives on a VPS
Put the secret in a file only root can read, and let systemd hand it to the process. This part outlives every SDK version, so it is worth doing once and properly.
sudo useradd --system --home /opt/claude-app --shell /usr/sbin/nologin claudeapp
sudo install -d -m 700 -o root -g root /etc/claude-app
sudo install -m 600 -o root -g root /dev/null /etc/claude-app/env
sudoedit /etc/claude-app/envThe file holds plain KEY=value lines. No export, no quotes, no shell syntax, because systemd parses it itself rather than running it through a shell.
ANTHROPIC_API_KEY=sk-ant-api03-REPLACE-ME
CLAUDE_MODEL=REPLACE-ME[Unit]
Description=Claude API service
After=network-online.target
[Service]
User=claudeapp
EnvironmentFile=/etc/claude-app/env
ExecStart=/opt/claude-app/venv/bin/python -m claude_app
Restart=on-failure
[Install]
WantedBy=multi-user.targetsystemd reads EnvironmentFile= as root, before it drops to User=claudeapp, so the service account never needs read access to the file. Mode 600 owned by root is enough, which is why the install command above sets it that way. Start it with sudo systemctl enable --now claude-app, then confirm with systemctl status claude-app that the unit reached active (running) rather than restarting in a loop.
Four things to avoid, each for a reason you can check yourself:
- Do not write the key with
Environment=inside the unit file. A unit under/etc/systemd/systemis world readable, sosystemctl cat claude-appprints the secret back to any local user. - Do not commit it.
.gitignorekeeps a new file out of a commit and does nothing about a file already committed, because git history keeps whatever it was given. - Do not bake it into a container image.
ENVlines and--build-argvalues are recorded in the image layers, anddocker history --no-truncprints them back. Deleting the file in a later layer does not remove it from the earlier one. Pass secrets at run time with--env-fileor a mounted file instead. - Do not treat the process environment as private from root.
sudo tr '\\0' '\\n' < /proc/$(pgrep -u claudeapp -f claude_app | head -1)/environprints the key back. The goal is to keep the secret away from every other account on the box, not from root, who can read it whatever you do.
That last point sets the boundary of what this design buys you. An environment variable is a fine container for a secret when the only things that can read it are the service and root. It is the wrong container when the process runs code you did not write, because anything the process can execute can read its own environment. Keeping secrets out of an AI agent's reach covers that case, which is a different problem with a different answer.
How do I rotate the key without downtime?
Rotate forward, and revoke last.
- Create the new key in the Console, in the same workspace as the old one.
- Write it into
/etc/claude-app/envwithsudoedit. - Run
sudo systemctl restart claude-app. - Confirm the service is answering requests, then revoke the old key in the Console.
EnvironmentFile is read when the unit starts, so a running process keeps the value it was given at launch. systemctl daemon-reload re-reads unit files and does not touch a running process's environment, so only a restart picks up the new key. Revoking in step 1 instead of step 4 buys you an outage that lasts until step 3.
The other three routes rotate at the provider. An IAM user supports two active access keys at once, so create the second, deploy it, then delete the first. A Google service account key rotates the same way. A Foundry key is regenerated in the portal, which invalidates the old one immediately, so write the new value before you click. Entra tokens and federated Anthropic tokens need no rotation at all, and that is the strongest argument for using them where you can.
While you are in the Console, set a spend limit on the workspace. A leaked key is expensive before it is anything else, and capping what an agent on a VPS can spend walks through the controls.
Why does my client return 401 or 403?
401 with authentication_error on the direct API. The key is wrong, revoked, or past its expiry. Expiry is the one people miss, because the code did not change and the request worked yesterday. Check the key's expiry column in the Console, or read expires_at from the Admin API, where it is null for keys with no expiry.
The SDK ignores your federation setup and uses a key instead. ANTHROPIC_API_KEY and ANTHROPIC_AUTH_TOKEN sit above federation in the credential precedence order, so either one shadows it. The sharp version of this: a variable exported as an empty string still occupies its slot, so ANTHROPIC_API_KEY="" makes the SDK authenticate with an empty key rather than fall through. Use unset ANTHROPIC_API_KEY.
401 with the bare message Authentication failed on federation. That message is deliberately identical for every possible cause, so a caller cannot probe your rule configuration by reading error text. The real reason is recorded on the authentication history page in the Console. Start there rather than guessing at the JWT.
403 on Foundry. The token authenticated but your Azure account lacks a role that permits the call. Assign an Azure RBAC role such as Foundry User (formerly Azure AI User) or Cognitive Services User to the identity making the request.
Anything on Bedrock. Run aws sts get-caller-identity as the service user first. It answers whether the box has usable AWS credentials at all, which separates a credential problem from a model access problem or a region mismatch. Model access is granted per region in the AWS console and is easy to enable in one region while calling another.
FAQ
Do I need an Anthropic API key to use Claude on Bedrock or Vertex?
No. On Amazon Bedrock the SDK signs each request with AWS credentials using SigV4, and on Google Cloud it sends a Google access token found through Application Default Credentials. No Anthropic-issued secret exists in either setup, and usage bills to the cloud account rather than to Anthropic. This is also why an Anthropic key left in ANTHROPIC_API_KEY is a hazard on those hosts: a generic client pointed at a cloud endpoint will happily send it there.
Is Claude available on Azure?
Yes, through Microsoft Foundry, formerly Azure AI Foundry. You create a Foundry resource, deploy a Claude model into it, and call https://{resource}.services.ai.azure.com/anthropic/v1/messages with either an Azure-issued key in an api-key header or a Microsoft Entra bearer token. Usage bills through the Azure Marketplace in Claude Consumption Units. The model field in the request body must carry your deployment name, which is only the same as the model identifier until you rename a deployment.
Where should I store the Claude API key on a Linux server?
In a file owned by root with mode 600, loaded through EnvironmentFile= in a systemd unit. systemd reads that file as root before it switches to the unit's User=, so the service account needs no access to it. Keep it out of the repository, out of the unit file itself (which is world readable and printed by systemctl cat), and out of container image layers, since docker history --no-trunc prints back anything set with ENV or --build-arg.
Why did my Claude API request start returning 401 when nothing changed?
The most common cause is a key that reached the expiry chosen when it was created. Expiry is set at creation, cannot be edited afterwards, and short-lived keys expire with no warning email. An expired key cannot be reactivated, so create a replacement, write it into the environment file, restart the service, and revoke the old key afterwards. If the key is definitely current, check that a stale credential is not shadowing it: ANTHROPIC_API_KEY set to an empty string still wins precedence over every other credential source.