Skip to main content
Reference

Grants reference

Grants provide credentials to container runs. Each grant type injects authentication for specific hosts. Credentials are stored encrypted on your host machine and injected at the network layer by Moat’s TLS-intercepting proxy. The container process does not have direct access to raw tokens.

Store a credential with moat grant <provider>, then use it in runs with --grant <provider> or in moat.yaml.

Grant types

GrantHosts matchedHeader injectedCredential source
githubapi.github.com, github.comAuthorization: Bearer ...gh CLI, GITHUB_TOKEN/GH_TOKEN, or PAT prompt
claudeapi.anthropic.comAuthorization: Bearer ...claude setup-token or imported OAuth
anthropicapi.anthropic.comx-api-key: ...API key from console.anthropic.com
openaiapi.openai.com, chatgpt.com, *.openai.comAuthorization: Bearer ...OPENAI_API_KEY or prompt
geminigenerativelanguage.googleapis.com (API key) or cloudcode-pa.googleapis.com (OAuth)x-goog-api-key: ... (API key) or Authorization: Bearer ... (OAuth)Gemini CLI OAuth, GEMINI_API_KEY, or prompt
graphiteapi.graphite.com, *.graphite.comAuthorization: token ...GRAPHITE_TOKEN, GT_TOKEN, or prompt
metagraph.facebook.com, graph.instagram.comAuthorization: Bearer ...META_ACCESS_TOKEN or prompt
npmPer-registry (e.g., registry.npmjs.org, npm.company.com)Authorization: Bearer ....npmrc, NPM_TOKEN, or manual
awsAll AWS service endpointsAWS credential_process (STS temporary credentials)IAM role assumption via STS
ssh:<host>Specified host onlySSH agent forwarding (not HTTP)Host SSH agent (SSH_AUTH_SOCK)
mcp-<name>Host from MCP server url fieldConfigured per-server headerInteractive prompt
gitlabgitlab.com, *.gitlab.comPRIVATE-TOKEN: ...GITLAB_TOKEN, GL_TOKEN, or prompt
brave-searchapi.search.brave.comX-Subscription-Token: ...BRAVE_API_KEY, BRAVE_SEARCH_API_KEY, or prompt
elevenlabsapi.elevenlabs.ioxi-api-key: ...ELEVENLABS_API_KEY or prompt
linearapi.linear.appAuthorization: ...LINEAR_API_KEY or prompt
vercelapi.vercel.com, *.vercel.comAuthorization: Bearer ...VERCEL_TOKEN or prompt
sentrysentry.io, *.sentry.ioAuthorization: Bearer ...SENTRY_AUTH_TOKEN or prompt
datadog*.datadoghq.comDD-API-KEY: ...DD_API_KEY, DATADOG_API_KEY, or prompt

Run moat grant providers to list all providers, including any custom providers you’ve added.

GitHub

CLI command

moat grant github

No flags. The command automatically detects your credential source.

Credential sources (in order of preference)

  1. gh CLI — Uses the token from gh auth token if the GitHub CLI is installed and authenticated
  2. Environment variable — Falls back to GITHUB_TOKEN or GH_TOKEN if set
  3. Personal Access Token — Interactive prompt for manual PAT entry

What it injects

The proxy injects an Authorization: Bearer <token> header for requests to:

  • api.github.com
  • github.com

The container receives GH_TOKEN set to a format-valid placeholder so the gh CLI works without prompting.

Refresh behavior

Tokens sourced from gh auth token or environment variables are refreshed every 30 minutes. PATs entered manually are static.

moat.yaml

grants:
  - github

Example

$ moat grant github

Found gh CLI authentication
Use token from gh CLI? [Y/n]: y
Validating token...
Authenticated as: octocat
GitHub credential saved

$ moat run --grant github ./my-project

Anthropic / Claude

Anthropic credentials are split into two separate grant types:

  • claude — OAuth tokens from Claude Pro/Max subscriptions. Restricted by Anthropic’s ToS to Claude Code only. Uses Authorization: Bearer auth.
  • anthropic — API keys from console.anthropic.com. Works with any tool or agent. Uses x-api-key auth.

Each command grants its own credential type. Both can coexist and moat grant list shows both.

CLI commands

moat grant claude       # OAuth token (for moat claude / Claude Code)
moat grant anthropic    # API key (for any agent or tool)

No flags.

moat grant claude

Presents a menu of OAuth token sources:

  1. Claude subscription — Runs claude setup-token to obtain a long-lived OAuth token. Requires a Claude Pro/Max subscription and the Claude CLI installed.
  2. Existing OAuth token — Paste a token from a previous claude setup-token run.
  3. Import existing credentials — Imports OAuth tokens from your local Claude Code installation.

Stored as claude.enc.

moat grant anthropic

Prompts for an API key directly, or uses ANTHROPIC_API_KEY from the environment.

Stored as anthropic.enc.

What it injects

The proxy injects credentials for requests to api.anthropic.com:

  • claude grant: Authorization: Bearer <token> with OAuth beta flag. Container receives CLAUDE_CODE_OAUTH_TOKEN placeholder.
  • anthropic grant: x-api-key: <key>. Container receives ANTHROPIC_API_KEY placeholder.

Refresh behavior

OAuth tokens imported from a local Claude Code installation do not auto-refresh. When the token expires, run a Claude Code session on your host to refresh it, then re-import with moat grant claude.

API keys do not expire.

moat claude grant resolution

When you run moat claude, the credential is selected automatically:

  1. If claude exists, use it (preferred for Claude Code)
  2. If only anthropic exists, use it as fallback
  3. If neither exists, error with instructions to run moat grant claude

Using both grants

You can grant both and use them together. This is useful when Claude Code needs its OAuth token and sub-tools in the same container need an API key:

moat run --grant claude --grant anthropic ./my-project

moat.yaml

grants:
  - claude        # OAuth token for Claude Code
  - anthropic     # API key for any tool

Backward compatibility

Existing users with an OAuth token stored under anthropic (the old single-provider model) are auto-migrated: moat claude detects the OAuth token prefix, copies it to claude.enc, and removes the old anthropic entry.

Examples

# Grant an OAuth token for Claude Code
$ moat grant claude

Choose authentication method:

  1. Claude subscription (OAuth token)
     Runs 'claude setup-token' to get a long-lived token.
  2. Existing OAuth token
  3. Import existing Claude Code credentials

Enter choice [1-3]: 1

Running 'claude setup-token' to obtain authentication token...
Credential saved to ~/.moat/credentials/claude.enc

# Grant an API key for general use
$ moat grant anthropic

Enter your Anthropic API key: ••••••••
Validating API key...
API key is valid.
Credential saved to ~/.moat/credentials/anthropic.enc

# Use Claude Code (picks up OAuth token automatically)
$ moat claude ./my-project

# Use both in a single run
$ moat run --grant claude --grant anthropic ./my-project

OpenAI

CLI command

moat grant openai

No flags.

Credential sources

  1. Environment variable — Uses OPENAI_API_KEY if set
  2. Interactive prompt — Prompts for an API key

What it injects

The proxy injects an Authorization: Bearer <token> header for requests to api.openai.com, chatgpt.com, and *.openai.com.

The container receives OPENAI_API_KEY set to a format-valid placeholder so OpenAI SDKs work without prompting.

Refresh behavior

API keys do not expire or refresh.

moat.yaml

grants:
  - openai

Example

$ moat grant openai
Enter your OpenAI API key.
You can find or create one at: https://platform.openai.com/api-keys

API Key: ••••••••
Validating API key...
API key is valid.

OpenAI API key saved to ~/.moat/credentials/openai.enc

$ moat codex ./my-project

Gemini

CLI command

moat grant gemini

No flags. The command detects whether Gemini CLI is installed and presents options accordingly.

Credential sources

  1. Gemini CLI OAuth (recommended) — Imports refresh tokens from a local Gemini CLI installation. Requires Gemini CLI installed and authenticated.
  2. API key — Enter an API key directly or set GEMINI_API_KEY in your environment.

What it injects

Gemini routes to different API backends depending on authentication method:

  • API key mode: The proxy injects an x-goog-api-key: <key> header for requests to generativelanguage.googleapis.com. The container receives GEMINI_API_KEY set to a placeholder value.
  • OAuth mode: The proxy injects Authorization: Bearer <token> for requests to cloudcode-pa.googleapis.com and handles token substitution for oauth2.googleapis.com. The container receives a placeholder oauth_creds.json in ~/.gemini/.

Refresh behavior

OAuth tokens are automatically refreshed by the proxy. Google OAuth tokens expire after 1 hour; Moat refreshes 15 minutes before expiry (every 45 minutes).

API keys do not expire.

moat.yaml

grants:
  - gemini

Example

$ moat grant gemini

Choose authentication method:

  1. Import Gemini CLI credentials (recommended)
  2. Gemini API key

Enter choice [1 or 2]: 1

Found Gemini CLI credentials.
Validating refresh token...
Refresh token is valid.

Gemini credential saved to ~/.moat/credentials/gemini.enc

$ moat gemini ./my-project

npm

CLI command

moat grant npm
moat grant npm --host=<registry-host>

Flags

FlagDescription
--host HOSTNAMESpecific registry host (e.g., npm.company.com)

Without --host, the command auto-discovers registries from ~/.npmrc and the NPM_TOKEN environment variable.

Credential sources

  1. .npmrc file — Parses ~/.npmrc for //host/:_authToken= entries and @scope:registry= routing
  2. Environment variable — Falls back to NPM_TOKEN for the default registry (registry.npmjs.org)
  3. Manual entry — Interactive prompt for a token

What it injects

The proxy injects an Authorization: Bearer <token> header for requests to each registered npm registry host. Each host gets its own credential — multiple registries are supported in a single grant.

The container receives a generated .npmrc at ~/.npmrc with:

  • Real scope-to-registry routing (npm needs this to resolve scoped packages)
  • Placeholder tokens (npm_moatProxyInjected00000000) — the proxy replaces Authorization headers at the network layer

Refresh behavior

npm tokens are static and do not refresh. If a token expires, revoke and re-grant.

Stacking

Multiple moat grant npm --host=<host> calls merge into a single credential. Each call adds or replaces the entry for that host. All registries are injected together at runtime.

moat.yaml

grants:
  - npm

Example

$ moat grant npm

Choose authentication method:

  1. Import from .npmrc / environment
     Found registries: registry.npmjs.org (default), npm.company.com (@myorg)
     To import a single registry, use: moat grant npm --host=<host>

  2. Enter token manually

Enter choice [1-2]: 1
Validating...
 registry.npmjs.org authenticated as "jsmith"
 npm.company.com authenticated as "jsmith"
Credential saved to ~/.moat/credentials/npm.enc

$ moat run --grant npm -- npm whoami
jsmith

Graphite

CLI command

moat grant graphite

No flags.

Credential sources

  1. Environment variable — Uses GRAPHITE_TOKEN or GT_TOKEN if set
  2. Interactive prompt — Prompts for a token with instructions to visit https://app.graphite.com/activate

What it injects

The proxy injects an Authorization: token <token> header for requests to api.graphite.com. Note: Graphite uses the token prefix, not Bearer.

The container receives a config file at ~/.config/graphite/user_config with a placeholder token so the Graphite CLI (gt) works without prompting.

Implied dependencies

Granting graphite automatically adds graphite-cli, node, and git as container dependencies. These are installed during image build.

Refresh behavior

Graphite tokens are static and do not refresh.

moat.yaml

grants:
  - graphite

Example

$ moat grant graphite
Enter a Graphite auth token.

To get your token:
  1. Visit https://app.graphite.com/activate
  2. Sign in and copy the token
  3. Paste it below

Token: ••••••••
Validating token...
Token validated successfully

$ moat run --grant graphite ./my-project

Meta

CLI command

moat grant meta

No flags.

Credential sources

  1. Environment variable — Uses META_ACCESS_TOKEN if set
  2. Interactive prompt — Prompts for an access token

The grant command validates the token against Meta’s Graph API before saving.

What it injects

The proxy injects an Authorization: Bearer <token> header for requests to:

  • graph.facebook.com
  • graph.instagram.com

No environment variables or config files are set inside the container.

Environment variables

VariablePurpose
META_ACCESS_TOKENAccess token — any Meta token type (see below)
META_APP_IDFacebook app ID — needed for token refresh
META_APP_SECRETFacebook app secret — needed for token refresh

All three can be set as environment variables or entered interactively. META_ACCESS_TOKEN is required. META_APP_ID and META_APP_SECRET are optional — the grant flow prompts for them after the token, and you can press Enter to skip.

Token types and refresh

The grant flow accepts any Meta access token. What happens next depends on which kind of token you provide and whether you also provide app credentials (app ID + app secret).

Long-lived user tokens with app credentials are the recommended setup. The token expires after ~60 days, but Moat exchanges it for a fresh one once per day, keeping it valid indefinitely. Generate a long-lived token by exchanging a short-lived one through the Graph API Explorer or your app’s OAuth flow, and provide your app ID and secret during moat grant meta. If a token is compromised, revoke it immediately in the Meta developer dashboard — do not rely on expiry.

Short-lived user tokens (from Graph API Explorer or an OAuth login flow) expire in 1—2 hours. If you provide app credentials, Moat exchanges the short-lived token for a long-lived one on the first refresh cycle, then refreshes it daily. This is a valid way to bootstrap — you do not need to exchange the token yourself first. Without app credentials, the token expires quickly and there is no way to extend it. This can provide additional security at the cost of capping the amount of time your agent will be able to run independently.

System user tokens never expire. They are created in Business Manager > System Users > Generate Token and are typically reserved for business owners or internal infrastructure. Because they never expire and cannot be scoped per-session, a leaked system user token has no automatic damage window. Prefer long-lived user tokens with app credentials for delegated access and agent use.

Summary:

Token typeApp credentials provided?What happens
Long-lived userYesExchanged for a fresh long-lived token once per day. Recommended.
Long-lived userNoWorks for ~60 days, then expires.
Short-lived userYesExchanged for a long-lived token on first refresh, then refreshed daily.
Short-lived userNoExpires in 1—2 hours. Requests fail after that.
System userNoToken never expires. No refresh needed.
System userYesUnnecessary but harmless. Refresh is a no-op.

API version

The provider uses Meta Graph API v25.0 by default. Override with the META_API_VERSION environment variable:

META_API_VERSION=v26.0 moat grant meta

moat.yaml

grants:
  - meta

Examples

User token with app credentials (recommended):

$ moat grant meta

Enter a Meta access token.

To create one:
  1. Go to https://developers.facebook.com/tools/explorer/
  2. Select your app and generate a token with the required permissions

Access token: ••••••••
Authenticated as: Jane Developer

Optional: provide app ID and app secret to enable automatic token refresh.
Press Enter to skip.

App ID (or Enter to skip): ••••••••
App secret: ••••••••
Token refresh enabled

$ moat run --grant meta ./my-project

Via environment variables:

$ META_ACCESS_TOKEN=EAAx... META_APP_ID=123456 META_APP_SECRET=abc123 moat grant meta

Using token from META_ACCESS_TOKEN environment variable
Authenticated as: Jane Developer
Found META_APP_ID and META_APP_SECRET for token refresh

$ moat run --grant meta ./my-project

AWS

CLI command

moat grant aws --role <ARN> [flags]

Flags

FlagDescriptionDefault
--role ARNIAM role ARN to assume (required)
--region REGIONAWS region for API callsFrom AWS config
--session-duration DURATIONSession duration (e.g., 1h, 30m, 15m)15m
--external-id IDExternal ID for cross-account role assumption
--aws-profile PROFILEAWS shared config profile for role assumption (falls back to AWS_PROFILE env var)

Credential source

Moat uses your host AWS credentials to call sts:AssumeRole. Your host must have valid AWS credentials (via aws configure, environment variables, or instance profile), and the target role must have a trust policy allowing your host identity to assume it.

If you use named AWS profiles, pass --aws-profile (or set AWS_PROFILE) at grant time. The profile is stored with the credential so the proxy daemon uses the correct source identity for role assumption, regardless of the daemon’s own environment.

What it injects

AWS credentials use credential_process rather than HTTP header injection:

  1. The role ARN and configuration are stored (not temporary credentials)
  2. When a run starts, Moat configures AWS_CONFIG_FILE in the container with a credential_process entry
  3. The credential_process command calls back to the proxy, which assumes the role and returns fresh temporary credentials
  4. The AWS SDK automatically calls the credential process when credentials expire

Note: The credential_process mechanism is accessible inside the container. Credentials are temporary (STS), sessions are short (default 15 minutes), and permissions are scoped to the assumed role.

Refresh behavior

The AWS SDK handles credential refresh automatically via credential_process. Each call to the process assumes a fresh role session with the configured duration.

moat.yaml

grants:
  - aws

Note: AWS-specific options (role, region, session duration, external ID) are configured at grant time with moat grant aws, not in moat.yaml. The moat.yaml grants field only specifies which grant types to use for a run.

Example

$ moat grant aws \
    --role arn:aws:iam::123456789012:role/AgentRole \
    --region us-west-2 \
    --session-duration 30m

Assuming role: arn:aws:iam::123456789012:role/AgentRole
Region: us-west-2
Session duration: 30m0s
Role assumed successfully
AWS credential saved

$ moat run --grant aws ./my-project

SSH

CLI command

moat grant ssh --host <hostname>

Flags

FlagDescription
--host HOSTNAMEHost to grant SSH access to (required)

Credential source

Uses your host’s SSH agent (SSH_AUTH_SOCK). The SSH agent must be running with keys loaded.

What it injects

SSH grants work differently from other grants. Instead of injecting HTTP headers, Moat proxies SSH agent requests:

  1. An SSH agent proxy starts inside the container
  2. The proxy connects to your host’s SSH agent
  3. Key listing and signing requests are forwarded, but only for keys mapped to the granted host
  4. Private keys never enter the container

Refresh behavior

SSH agent requests are forwarded in real time. No refresh mechanism is needed.

moat.yaml

grants:
  - ssh:github.com

The host name is part of the grant identifier, separated by a colon.

Example

$ moat grant ssh --host github.com

Using key: user@host (SHA256:...)
Granted SSH access to github.com

$ moat run --grant ssh:github.com -- git clone git@github.com:my-org/my-project.git

MCP

CLI command

moat grant mcp <name>

The <name> argument matches the MCP server name in moat.yaml. The credential is stored as mcp-<name>.

Credential source

Interactive prompt for a credential value (hidden input).

What it injects

The proxy injects the credential into the HTTP header specified by auth.header in the MCP server configuration. Injection occurs for requests matching the host in the MCP server’s url field.

Refresh behavior

MCP credentials are static. Revoke and re-grant to update them.

moat.yaml

MCP grants are referenced in the top-level mcp: field, not in grants::

mcp:
  - name: context7
    url: https://mcp.context7.com/mcp
    auth:
      grant: mcp-context7
      header: CONTEXT7_API_KEY

Example

$ moat grant mcp context7
Enter credential for MCP server 'context7': ••••••••
MCP credential 'mcp-context7' saved

$ moat claude ./my-project

Using grants in runs

Via CLI flags

Pass --grant one or more times:

moat run --grant github ./my-project
moat run --grant github --grant anthropic ./my-project
moat run --grant ssh:github.com ./my-project

Via moat.yaml

List grants in the grants field:

grants:
  - github
  - anthropic
  - openai
  - npm
  - ssh:github.com

Grants from CLI flags are merged with those in moat.yaml.

Multiple grants

Combine any number of grants in a single run:

grants:
  - anthropic
  - github
  - ssh:github.com
  - aws
moat claude --grant github --grant ssh:github.com ./my-project

Each grant type injects credentials independently. The proxy matches requests by host and injects the appropriate headers.

Managing grants

List stored grants

moat grant list
moat grant list --json

# List grants in a specific profile
moat grant list --profile work

Revoke a grant

moat revoke github
moat revoke claude
moat revoke anthropic
moat revoke npm
moat revoke ssh:github.com
moat revoke mcp-context7

# Revoke from a specific profile
moat revoke github --profile work

This deletes the encrypted credential file. Future runs cannot use the credential until you grant it again.

Credential profiles

Profiles maintain separate sets of credentials. Use one profile for personal projects and another for work.

Setting a profile

Set the active profile with the --profile global flag or the MOAT_PROFILE environment variable:

# Grant a credential to a profile
moat grant github --profile work

# Use profile credentials in a run
moat run --grant github --profile work

# List profile credentials
moat grant list --profile work

# Set via environment variable
export MOAT_PROFILE=work
moat grant github
moat run --grant github

Storage layout

Profile credentials are stored separately from the default credential store:

~/.moat/credentials/           # Default (no profile)
~/.moat/credentials/profiles/
  work/                        # "work" profile
  personal/                    # "personal" profile

Each profile has its own isolated set of encrypted credential files. Granting a credential in one profile does not affect another.

Profile names

Profile names must start with a letter or digit and contain only letters, digits, hyphens, and underscores.

Valid: work, my-project, team_alpha, prod1 Invalid: -leading-dash, has spaces, uses.dots

Credential storage

Credentials are stored encrypted in ~/.moat/credentials/ (or ~/.moat/credentials/profiles/<name>/ when using a profile). See Credential management for encryption and storage details.

Config-driven providers

The providers from gitlab through datadog in the grant types table are defined as YAML configurations shipped with the binary. They work the same as the Go-implemented providers — credentials are stored encrypted and injected at the network layer — but are defined declaratively.

Use them the same way:

moat grant gitlab
moat run --grant gitlab ./my-project

Custom providers

Add your own providers by creating YAML files in ~/.moat/providers/. Each file defines a provider with host matching rules, header injection, and credential sources. See Provider YAML reference for the full schema.

Listing providers

List all available providers (built-in, packaged, and custom) with:

moat grant providers
moat grant providers --json