OpenAI key types — project, service account, admin
Last verified
Quick answer: sk-proj-… makes model calls. sk-admin-… reads billing and manages keys. They are not interchangeable, and one of them is much more dangerous than the other.
The types
| Prefix | Type | Scope | Can do |
|---|---|---|---|
sk-proj-… | Project API key | One project | Inference |
sk-admin-… | Admin key | Organisation | Usage, cost, key/project/member management |
sk-… (legacy) | User key | Personal account | Inference — being phased out in favour of project keys |
Service accounts are a variation on the first: a project key that belongs to a service rather than a person, so it survives that person leaving the company. If a key is in production and tied to an individual's account, it is one offboarding away from an outage.
Which do you need?
Calling a model → project key. Never an admin key; admin keys cannot do inference anyway.
Reading spend → admin key. A project key cannot see billing data at all — this is the wall most people hit when they first try to build a cost dashboard.
Revoking keys programmatically → admin key.
A production service → a project key belonging to a service account, one per service, so you can attribute usage and revoke precisely.
Blast radius
The reason to care about the distinction:
A leaked project key means someone can spend your money on inference. Bad, bounded by your spend limit, fixed by revoking that key.
A leaked admin key means someone can read all your billing data and delete every key in the organisation — an instant, total outage across everything you run. It cannot spend money on tokens directly, so the damage is disclosure and destruction rather than cost.
The practical rule that falls out of this: never store an admin key next to your inference keys. Different secret store, different environment, different repository. Otherwise a single leak escalates from "rotate one key" to "the whole organisation".
One key per service
Not bureaucracy — it is what makes an incident survivable.
With one shared key everywhere, "this key is compromised" means taking down everything at once and having no idea which service was the source. With one key per service, you can see which key generated the anomalous usage, revoke just that one, and keep the rest running.
It also gives you per-service cost attribution for free, via group_by=api_key_id on the Usage API — no proxy, no code changes.
Naming
Name every key at creation for where it will live: api-prod, worker-batch, ci-integration-tests, tkn-ios. Not key1, not test, not blank.
The full value is shown once and never again. Six months later you'll be looking at a list of redacted keys trying to work out which is safe to delete, and the name is the only thing that answers it. An unnamed key you're afraid to delete is worse than no key.