Blog

One Identity per Workload: Projects, Service Accounts, and the Key You Should Not Share

A shared developer key running in production is an identity design problem, not a storage problem. This guide maps workloads to identities, explains what key scopes can and cannot do, and keeps Admin keys away from application runtimes.

One Identity per Workload: Projects, Service Accounts, and the Key You Should Not Share

An API key is a secret credential that authenticates a request. A service account is a nonhuman identity that an application or an automated workload can use, and it can own keys of its own. The two are different parts of the same authentication setup, and most teams blur them together: they treat the key as the thing to protect and never ask whose identity sits behind it.

That question has a practical test. If the person whose key runs in production left tomorrow, would anything stop working? If the answer is yes, the workload is running on a human identity, and better secret storage will not change that. The key would still belong to someone who no longer works there, carrying the access they had on their last day.

This article is about choosing the identity before the credential. It follows the official guidance on API keys and service accounts and stays at the level of architecture: which workload should act as whom, and what the permission model will and will not allow.

A credential you rotate, an identity you retire

An API key is a string. You can issue it, scope it, expire it, and revoke it, and a replacement can take its place without anything else changing. A service account is a durable object: a nonhuman identity inside a project, with roles attached, able to own keys over its lifetime.

Keeping those apart explains why rotation and offboarding are different operations. Rotation replaces a string and leaves the identity in place. Offboarding removes an identity and takes every credential attached to it out of service at once. A team that keeps production credentials in a person's account can do the first but not the second. There is no way to retire the identity without either breaking the application or leaving a departed employee's access alive.

Which identity does this workload need?

The official guidance maps workloads to identities, and most arguments about keys turn out to be arguments about which row a system belongs in.

WorkloadIdentity and credentialWhy
Your own local development or explorationYour user account and a project API keyRequests use your access to the project.
A deployed application, scheduled job, or CI workflowA project service account and its API keyThe workload has an identity separate from an individual developer.
A workload with an identity provider that OpenAI supportsA project service account with workload identity federationThe workload can authenticate without storing a long-lived OpenAI API key.
Automation that creates projects or manages their service accounts and keysAn Admin API keyOrganization management uses separate administrative endpoints and permissions.

Read the table as a description of responsibility rather than a ranking. A project API key under your user account is the right credential for your own exploration. The same key inside a scheduled job is a design error, because the job now depends on one person's continued employment and inherits everything that person can reach. The CI row carries the same logic in a different place: a pipeline authenticating with a developer's key breaks the week that developer changes teams. Giving the pipeline its own service account separates the build's permissions from any individual's, which is the point of the coding agents in CI discussion.

Workload identity federation matters even if you are not ready to use it. When the workload runs somewhere with an identity provider that OpenAI supports, it can present that identity and receive access without a long-lived key stored next to the code. A key you never hold cannot leak into a build artifact or an environment file.

What a user-owned key actually carries

The documentation states the rule plainly: a user-owned key acts with its owner's access, so you should not share one developer's key across a team or use that person's identity as the permanent identity of a production application.

That sentence closes a common loophole. Sharing a key is usually discussed as a hygiene problem, something to fix with a password manager and a stern note. Within the permission model it is a different category of mistake: every request made with that key is made as the person who owns it. Logs record a human who may have had nothing to do with the call. An access review shows the application holding whatever that person accumulated over their tenure. A change in their role silently changes what production can do.

The permanence is worse. A developer's key can be fine for a week of debugging on a shared staging box. As the long-term identity of a production system, it means the system's permissions are a person's permissions, and its lifecycle is somebody's career. Teams that would never hard-code a password into a repository often accept this arrangement because the key was convenient at the time and nothing has broken since.

Projects set the outer boundary, service accounts the inner one

A service account belongs to a project. That containment does most of the structural work: separate projects isolate resources, membership, and usage, which is why separate applications and environments are usually split at that level. Inside a project, separate service accounts distinguish workloads without requiring another project for every job.

The practical rule is to draw the isolation boundary where the answer to "who can see whose data" changes, not where the org chart changes. Two teams working on the same dataset can share a project and still hold separate service accounts for their own jobs. Two applications that must never read each other's data need separate projects, even when the same people operate both. The boundary reasoning in multi-tenant LLM apps applies at a smaller scale here: isolation follows the data, and the identity is how the isolation becomes enforceable.

API key scopes narrow; they never grant

An API key's permissions and its owner's permissions both matter. Key scopes can restrict what the key can do, but they do not grant anything the user or service account lacks. An unrestricted key still operates inside its owner's effective permissions.

That single rule explains the complaints that come up most often. A key issued with the narrowest scope can still perform an action you did not expect, because its owner could perform that action all along. The scope removed possibilities; it did not create them. A key issued with a generous scope returns 403 on the call you wanted, because the owner never had that permission, and widening the key cannot fix it.

The order of operations follows from this. Assign the service account the roles its workload needs, then restrict its keys where that adds a useful limit. Doing it the other way, granting broad roles and hoping scope will trim them, leaves the identity more powerful than the workload, and the next key issued without care inherits that surplus.

The Admin key is not a workload key

An Admin API key serves organization management through separate administrative endpoints and permissions. A project workload key serves model requests. The official guidance keeps them apart for a reason: keep the administrative credential in the trusted service that provisions and manages resources, and give the application the project's service-account credential.

Mixing them is the most direct route to over-privilege. A runtime holding an Admin key can create projects, create service accounts, and issue keys. A bug in that runtime is no longer a bug in one application; it is an organization-level action. Administrative credentials belong where provisioning happens, in a service whose job is to create and manage resources. The application that sends model requests never needs one, and an Admin key has no place in a browser, a mobile app, or any client-side bundle.

Where programmatic provisioning trips people up

Automating projects and credentials is legitimate, and the documented path is the Admin API. A provisioning service can create an OpenAI service account inside a project, assign it a role, and issue a scoped key for the workload that will use it. A few details cause most of the confusion.

create_service_account_only creates a service account without default roles and without an initial key. That is useful when you want to assign a custom role before anything can be used, and it is also where teams get stuck: the account exists, looks ready, and returns 403 on its first request. Creating a service account without default roles does not grant it access to make model requests. Assign the required permissions before you use its credentials.

Organization and project key policies still apply to key creation. A service account does not bypass them, so a provisioning flow that works in a permissive project can fail in a locked-down one for policy reasons rather than code reasons.

The project key management endpoints do not create user-owned project keys on behalf of another person. For programmatic key creation, the documented route is a service account and its key creation endpoint. A provisioning system that tries to mint personal keys for other people is working against the model, and it will keep finding edges that do not behave the way a service account does.

The identity stays, the key changes

The API returns the full value of a service-account key only when you create it. After that you are working with a reference, not the secret. Save it to a secret manager at creation time and inject it into the workload on the server. Keep it out of browser code, mobile apps, source control, logs, and any response a customer can see. The leak paths covered in API key hygiene for LLM apps still apply: a service-account key pasted into a notebook is exposed like any other.

Set an expiration that matches your organization's and project's policies. Rotation then becomes a key operation rather than an identity migration: issue a replacement, update the workload, verify that requests succeed, and revoke the previous key. The service account keeps its roles and its history throughout. The rotation walkthrough for coding-agent credentials covers that sequence in practice, and the identity decision is what makes it safe to repeat on a schedule.

Access deserves the same treatment as the key. When a workload's responsibilities change, review what its service account can reach. When the workload is retired, revoke its credentials. An identity that outlives its job is the same problem as a key that outlives its owner, one level up.

An audit you can finish this week

The list below is short on purpose: enough to find the identity errors that hurt most, and concrete enough to act on.

  • List every key in production and mark whether it belongs to a person or to a service account.
  • Replace any personal key in production with a project service account.
  • Give CI its own service account instead of reusing a developer's key.
  • Where a supported identity provider is available, use workload identity federation and stop storing a long-lived key.
  • Assign the service account its roles first, then narrow key scopes.
  • Keep administrative operations on a separate Admin key held only by the provisioning service.
  • Verify that a newly created service account can actually make requests; one without roles will return 403.
  • Save the full key value at creation, because it will not be shown again.

The one key across coding tools pattern is a reasonable setup for a single developer working locally, and it is exactly the pattern to stop copying once the work moves into a shared environment. Ask instead how many people and workloads depend on the same identity.

What a service account does not fix

A service account is an identity, not a security upgrade. An account created without roles cannot make requests; an account with broad roles can do too much; a key pasted into a client bundle leaks the same way a personal key would. The improvement comes from the separation itself. Permissions attach to a workload, so an access review has a subject that is not a person, a key can be replaced without a migration, and someone leaving the company no longer changes what production can do.

That is the test to keep. If the answer to "would production stop if this person left" is still yes, the identity work is not finished. Check the details against the official documentation as it stands; the behavior described here was checked on 2026-11-05.