Skip to content
Latchkey LogoLatchkey home

One scoped API key per coding agent

An API key per coding agent, scoped to the repositories and actions its job needs and set to expire, means one leak or one runaway loop is traced and revoked without touching anything else. On GitHub that means a fine-grained personal access token or a GitHub App installation token; on Latchkey it is one API key per agent, created from an access preset with an expiry.

The default is one token pasted into every tool: the CLI on the laptop, the agent in the editor, the bot in CI. It works until something goes wrong, and then nobody can say which of them ran the job, and revoking the token breaks all of them at once.

One key per agent fixes both. Before you mint one, answer four questions for that agent: what may it read, what may it start, what may it spend, and when should it stop working. The rest of this page is where each answer goes on GitHub and on Latchkey.

Three agents, three keys: each key mapped to its scopes, expiry and what a leak of it would allow
A key per agent turns "what did this token do" into a question with one answer, and makes revocation a local event.

GitHub option 1: a fine-grained personal access token

A fine-grained token is limited to resources owned by a single user or organization, can be limited further to specific repositories, and carries specific permissions rather than broad scopes. Organization owners can require approval before one works. To dispatch a workflow, the endpoint needs the Actions repository permission at write.

GitHub lets you share a token template as a link with the fields filled in. expires_in takes 1 to 366 days or none; infinite lifetimes are allowed unless an organization or enterprise policy blocks them. Mint one link per agent role and keep it in your runbook.

Two catches. A token acts with its owner's identity, capped by its permissions, so a run it starts reads as yours. And fine-grained tokens cannot call the Checks API, which is why gh run watch says in its own help that it does not support them.

Token template URL (line breaks for legibility)
https://github.com/settings/personal-access-tokens/new
  ?name=agent-claude-ci
  &description=Dispatch+and+read+CI+for+one+repository
  &target_name=acme
  &expires_in=30
  &actions=write
  &contents=read

GitHub option 2: a GitHub App installation token

An App gives the agent an identity of its own, and its installation tokens expire after one hour. When you mint one you can narrow it to named repositories and to a subset of the App's permissions; it can never exceed what the installation was granted.

Inside a workflow, actions/create-github-app-token does the minting. Each permission is its own permission-<name> input, and the README recommends listing them explicitly because by default the token inherits every permission of the installation. The token is revoked when the job completes unless you set skip-token-revoke.

.github/workflows/agent.yml
steps:
  - id: agent-token
    uses: actions/create-github-app-token@64ab729a6294000f5aa4a4bc0b73d1f32bc45510 # v3.3.0
    with:
      client-id: ${{ vars.AGENT_APP_CLIENT_ID }}
      private-key: ${{ secrets.AGENT_APP_PRIVATE_KEY }}
      repositories: api
      permission-actions: write
      permission-contents: read
  - run: gh workflow run agent-check.yml --ref "$GITHUB_REF_NAME"
    env:
      GH_TOKEN: ${{ steps.agent-token.outputs.token }}

Which GitHub credential fits which agent

Pick by how long the agent lives and whose name its runs should carry.

CredentialLifetimeActs asWatch out for
GITHUB_TOKENUntil the job finishes, or its maximum lifetimeThe workflowIts events start no run, except dispatch events and approval-required pull_request runs
Fine-grained PAT1 to 366 days, or none if policy allowsThe human who made itNo Checks API, so no gh run watch
App installation token1 hourThe AppInherits every installation permission unless narrowed

Latchkey: one key per agent, from a preset

Latchkey keys are minted in Settings, API keys and agents, by owners and admins. Name the key after where it will live, pick an expiry (Never is the default; 30, 60 or 90 days and 1 year are the others), and pick its access. Read-only is the default. Every write is a separate opt-in fixed at creation: an existing key cannot be upgraded, so a new need means a new key.

Keys are workspace-scoped, shown in full once, stored hashed, and revocable at any time; a revoked key loses access within a minute and stays listed for audit.

PresetAddsA leaked key can additionally
Read-only (default)mcp:readRead CI failure and run data, including log excerpts
CLI runnerjobs:run (with jobs:read)Run commands on billable runners
Dispatchmcp:dispatchTrigger any dispatchable GitHub Actions workflow in monitored repositories
CLI runner and secretssecrets:writeSet or replace any workspace or repository secret, never read one back
Approve runsjobs:approveApprove a waiting run for any environment
Migration pull requestsmigration:applyOpen a migration pull request
Full accessEvery scope, including workflows:runAll of the above, and start any Latchkey CI workflow

Match the preset to the agent

A triage agent that reads failures needs Read-only. An agent that verifies its own edits needs CLI runner. An agent that re-runs a GitHub Actions workflow after a fix needs Dispatch, and nothing more. Keep Full access away from agents: Latchkey's docs point out that a key with jobs:approve held by an agent that can also push can approve the runs of its own changes, with no other person deciding.

Hand the key to the agent through its environment rather than a file. The CLI reads LATCHKEY_TOKEN, and a read-only status probe checks the key and its scope without creating anything.

Terminal
# in the shell that starts the agent, one key per agent
export LATCHKEY_TOKEN=lk_live_...   # key named "claude-code on build box", CLI runner, 30 days
latchkey status cli-00000000-0000-0000-0000-000000000000
# "Job cli-... was not found in this workspace." means the key works and carries jobs:read

See what each key ran

Attribution is the reason for all of this, so check it. Settings has an Agent activity tab that shows what each agent's key ran. In the terminal, latchkey list prints a KEY column naming the key that started each job, and the API keys table shows when each key was last used, flagging one that has gone unused for 30 days.

A key has no spending budget of its own. Bound an agent's spend through its work instead, with a batch budget named for the agent, as cap agent CI spend shows. When an agent starts getting 401s, the key was revoked or reached its expiry: mint a new one rather than widening an old one.

Documentation, not a captured session

No recorded run sits behind this page, and the repository's evidence files have nothing for it. The GitHub rows come from docs.github.com (personal access tokens, installation tokens, GITHUB_TOKEN) and actions/create-github-app-token at v3.3.0; the Latchkey rows from its connect-your-agent and security pages and the SKILL.md in CLI 0.16.0, all read 2026-11-12. A key minted for a demonstration would prove that one key works, not what a preset allows.

Key takeaways

  • One key per agent makes "which agent ran this" answerable and revocation local.
  • Fine-grained PATs carry your identity and cannot call the Checks API; App tokens carry the App's and last one hour.
  • Latchkey keys are read-only by default; each write is chosen at creation and cannot be added later.
  • Keys have no budget of their own: bound spend with a batch named for the agent.

Frequently asked questions

Should I use a GitHub App or a personal access token for automation?
For long-lived automation, an App: its installation tokens expire after an hour, carry the App's identity rather than a person's, and can be narrowed per token. A fine-grained PAT is simpler to create and fits a single person's agent, but runs it starts are attributed to that person.
How long does a GitHub App installation token last?
One hour. The token response includes the time it expires, and the Octokit SDKs regenerate it automatically when it does.
Can a fine-grained personal access token trigger a workflow?
Yes, if it has the Actions repository permission at write on that repository, which is what the workflow dispatch endpoint requires. The workflow must also declare on: workflow_dispatch.

References

Every agent its own key. Latchkey keys start read-only, expire on schedule, and revoke in a minute. Start free → 30-day trial · No credit card