TL;DR
A Hugging Face access token is a credential, full stop. Treat it like a registry pull secret: scope it down, keep it in a secret manager, rotate it, and never bake it into an image or commit it. The CLI is now hf (the old huggingface-cli still works but warns you). Log in once with hf auth login. Use a read token on workstations and a fine-grained token for anything in CI or production. If your organization enforces a fine-grained policy, plain read and write tokens get a 403 against that org.
Here is the first thing that goes wrong on a fresh box. Someone tries to pull a gated or private model and gets this:
$ hf download meta-llama/Llama-3.1-8B-Instruct config.json
...
huggingface_hub.errors.GatedRepoError: 401 Client Error.
Cannot access gated repo for url https://huggingface.co/...
Access to model meta-llama/Llama-3.1-8B-Instruct is restricted.
You must be authenticated to access it.
That 401 is not a bug. It is access control doing its job. The fix is not a workaround, it is understanding that your Hugging Face account is an identity and the token is the credential that proves it. Get the credential model right once and the rest of this series stops fighting you. If you are still finding your way around the platform itself, Part 2 on navigating the Hub and reading model cards sets the scene; this part is about getting in the door safely.
Your account is an identity, the token is the credential
Strip away the friendly branding and the Hub is a registry. It holds artifacts (model and dataset repos), it has public and private visibility, and it gates some artifacts behind an access request. To pull anything that is not fully public, you authenticate. The token is how you do that from a machine. It is the exact equivalent of the pull secret you already hand to a Kubernetes node so it can pull from your container registry. Same job, same risks, same rules.
You generate tokens on the Hub at Settings, then Access Tokens. Each token is a long string that starts with hf_. Anyone holding that string acts as you, within the token scope, until it is revoked. There is no second factor on a token once it exists. That single fact should shape every decision that follows: a token is a bearer credential, so the entire game is limiting what it can do and limiting how long a leaked one stays useful.
Three token types, and which to use in production
Hugging Face gives you three kinds of token. The difference is exactly the difference between a broad service account and a least-privilege one, so you already have the instinct for this.
| Token type | What it can do | Use it for | Blast radius if leaked |
|---|---|---|---|
| Read | Pull any public, private or gated repo you can already read. | Workstations, read-only CI, inference servers pulling weights. | Attacker can read every private repo you can. |
| Write | Everything read can, plus push, create and edit repos you can write to. | Publishing models or datasets, release pipelines. | Attacker can overwrite or delete your models. |
| Fine-grained | Exactly the repos and permissions you select, nothing else. | Production, CI, anything shared or automated. | Limited to the repos and scopes you picked. |
My position is simple. People on laptops can use a read token because the convenience is worth it and the scope is read-only. Everything automated, everything shared, and everything that writes should be a fine-grained token pinned to the specific repos it touches. Hugging Face itself recommends fine-grained tokens for production for the same reason you scope an IAM role: when it leaks, and one eventually will, the damage is bounded to what you granted. The one place I would not reach for fine-grained is a quick throwaway on your own machine where the ceremony of picking repos is not worth it. Validate first by checking whether your org has a token policy, because that decides the question for you.
Installing and logging in with the hf CLI
The command line tool ships inside the huggingface_hub Python package, and it was renamed. What used to be huggingface-cli is now just hf, with a cleaner hf <resource> <action> grammar. The legacy name still works for now, but it prints a deprecation warning and points you at the new form. Install or upgrade, then check the version:
$ pip install -U huggingface_hub
$ hf version
huggingface_hub version: 1.0.0
All the authentication commands now live under hf auth:
$ hf auth login # log in and store a token
$ hf auth whoami # confirm which account you are
$ hf auth list # list stored tokens / profiles
$ hf auth switch # switch between stored profiles
$ hf auth logout # remove the stored token
Run hf auth login on a workstation and it walks you through a browser flow: it prints a URL and a short code, you open the URL, enter the code, approve, and the token is retrieved and saved locally. After that, hf download and the Python libraries pick the token up automatically. The same 401 from the opener now succeeds, assuming you have also accepted the gated model terms on its repo page.
Where the token lives on disk
After login, the token is written to disk in plain text under your Hugging Face home directory, by default ~/.cache/huggingface/token. The location is controlled by the HF_HOME environment variable. The libraries also read a token straight from the HF_TOKEN environment variable, which takes priority and is the right path for servers and pipelines. Knowing both of these is the difference between treating a token as a magic setting and treating it as a secret with a known location on every box.
hf auth login on shared CI runners. Inject HF_TOKEN from your secret manager for that job only, so nothing lands on disk and the value disappears when the job ends.Tokens in CI and automation
The interactive browser flow is useless to a pipeline. For automation, pass the token without prompting. The clean pattern is to read it from a secret store into the environment as HF_TOKEN and let the tooling pick it up. If you must call the login command non-interactively, it accepts the token directly:
# Preferred: let the libraries read HF_TOKEN from the env
export HF_TOKEN=hf_xxxxxxxxxxxxxxxxxxxxx
hf download my-org/internal-model --local-dir ./model
# Or log in explicitly without a browser prompt
hf auth login --token $HF_TOKEN
# Never do this: a token in plain command history / logs
hf auth login --token hf_realSecretPastedInline # leaks into shell history
Worked example
A nightly job pulls an internal model into a build. In the CI settings you store a fine-grained token scoped to read only my-org/internal-model, exposed to the job as HF_TOKEN. The job step is just:
$ hf download my-org/internal-model --local-dir ./model
Fetching 7 files: 100%|...| 7/7
./modelExpected result: the files land in ./model and nothing secret is printed. Failure mode: if the token is missing or wrong you get 401 Client Error: Repository not found or you do not have access. If the token is valid but scoped to the wrong repo, you get a 403, not a 401, which is your tell that authentication worked but authorization did not.
Rotation, revocation, and org token policy
Tokens do not rotate themselves. You revoke and reissue them from the same Settings page where you created them, and a revoked token stops working immediately. The discipline is the same one you apply to any long-lived credential: issue with an owner and a purpose, rotate on a schedule, and revoke the moment someone leaves or a value might be exposed. Because fine-grained tokens are per-workload, you can rotate one pipeline without breaking the other twelve, which is the whole point of scoping them narrowly in the first place.
At the organization level there is a control worth knowing. An org can set a policy that requires fine-grained tokens. When that is on, a plain read or write token used against that org returns a 403, and a fine-grained token scoped to an org that requires approval sits in a pending state until an administrator approves it. That is governance you can enforce, and it parallels exactly what you already do with a registry: project-level access control, robot accounts, and approval before anything privileged is handed out. If you want the same controls applied to container images rather than models, the registry side of this is covered in the Harbor for Beginners guide, and the broader concept of credential scoping in AI systems sits in the GenAI series.
Multiple identities, profiles, and the whoami habit
Most platform engineers end up with more than one Hugging Face identity. There is your personal account, and there is the bot or service identity that owns the org tokens your pipelines use. Mixing them is how a model gets pushed under the wrong owner, or a personal token ends up doing production work it should never touch. The CLI handles this the way kubectl handles contexts. After you log in with more than one token, hf auth list shows the stored profiles and hf auth switch changes the active one, so you can keep a read-only personal login and a scoped org login side by side and pick the right one per task.
The habit that prevents the worst mistakes is cheap: run hf auth whoami before anything that writes. It prints the account the active token belongs to and the orgs it can act for. Treat it the same way you glance at your current kubectl context before running a destructive command. One extra line in a script, and you never push to the wrong namespace again.
$ hf auth whoami
your-bot-account
orgs: my-org
$ hf auth switch # pick the profile you want
$ hf auth list # see every stored token and which is active
On a server, you skip profiles entirely and let HF_TOKEN decide the identity for that one process. That is cleaner than stored profiles because the credential lives only for the life of the job and the identity is whatever you injected, with nothing left behind to switch away from later.
What I’d Do
Default to fine-grained tokens for everything that is not a human on a laptop. Why: when one leaks, the damage is bounded to the repos and permissions you granted, which is the only property that reliably saves you. When not: a quick read-only pull on your own machine does not need the ceremony, so a read token is fine there. What to validate first: check whether your org enforces a fine-grained policy, because if it does, the decision is made and your read and write tokens will start returning 403 anyway.
The mental shift that makes all of this easy is the one I opened with: a Hugging Face token is not a setting, it is a credential, and you already know how to run credentials. Scope them, store them in a secret manager, keep them out of images and shell history, rotate them, and put an owner on each one. Do that and the Hub behaves like a well-run registry instead of a place where secrets quietly leak. Next time you wire a model pull into a pipeline, set HF_TOKEN from your vault and confirm nothing lands on disk.
References
- Say hello to hf: a faster, friendlier Hugging Face CLI
- User access tokens (Hugging Face Hub docs)
- Command Line Interface (huggingface_hub docs)


DrJha