Custom Models (BYOK)

Connect your own API keys, use open source models, or run local models

The Droid CLI supports custom model configurations through BYOK (Bring Your Own Key). Use your own OpenAI or Anthropic keys, connect to any open source model providers, or run models locally on your hardware.

For Factory-managed models and multipliers, see Available Models. For org-level restrictions on custom models, base URLs, and provider access, see Enterprise Controls.

Note

Your API keys remain local and are not uploaded to Factory servers. Personal custom models are available in the Droid CLI and the desktop app, which read your local settings.json; they don't appear in Factory's hosted web or mobile platforms.

Install the CLI with the 5-minute quickstart →

Add a custom model

  • CLI: run /settings and open Custom models, or run /model and select Manage custom models. Choose Add model, pick a provider, fill in the fields, and select Save changes.
  • Desktop app: open Settings > Custom Models and click Add Model. Fill in the Form view, or switch to JSON to edit the model object directly.
  • Settings file: add an entry to customModels in ~/.factory/settings.json, as described below.

Both editors save to ~/.factory/settings.json and list only your personal models, not organization or project models. Saved models appear under Custom Models in /model, labeled with their displayName.

Configuration reference

Add custom models to ~/.factory/settings.json under the customModels array:

JSON
{
  "customModels": [
    {
      "model": "your-model-id",
      "displayName": "My Custom Model",
      "baseUrl": "https://api.provider.com/v1",
      "apiKey": "${PROVIDER_API_KEY}",
      "provider": "generic-chat-completion-api",
      "maxOutputTokens": 16384
    }
  ]
}
Tip

In settings.json (and settings.local.json), apiKey supports environment variable references using ${VAR_NAME} syntax. For example, "apiKey": "${PROVIDER_API_KEY}" reads from the environment variable named PROVIDER_API_KEY (for example: export PROVIDER_API_KEY=your_key_here).

Note

apiKey is optional for HTTP custom models. Omit it entirely for a keyless endpoint (for example, a gateway that authenticates via extraHeaders or network-level trust), or use apiKeyHelper to mint a short-lived token at request time. Some OpenAI-compatible servers require a non-empty apiKey even if they ignore its value; in that case, set any placeholder. baseUrl is still required for non-Bedrock models.

Note

Legacy config.json: When Droid starts, it copies custom models from ~/.factory/config.json (snake_case fields such as custom_models and base_url) into settings.json and leaves config.json unchanged. Make later changes in settings.json; Droid ignores edits to models it has already copied.

Supported fields

modelstringrequired

Model identifier sent via API (e.g., claude-sonnet-4-5-20250929, gpt-5.3-codex, qwen3:4b).

displayNamestring

Human-friendly name shown in model selector.

baseUrlstring

API endpoint base URL. Required unless the model has a bedrock block.

apiKeystring

Your API key for the provider. Optional: omit it for a keyless endpoint, or use apiKeyHelper instead. If set, it can't be empty. Supports environment variable references; see the Tip above.

apiKeyHelperstring

Shell command whose stdout is used as the API key, resolved at request time and refreshed on a TTL and after a 401. Takes precedence over apiKey. Honored in user and org-managed settings, but not in project or folder settings; see Dynamic credentials.

apiKeyHelperTtlMsnumber

Refresh interval for apiKeyHelper output, in milliseconds. Defaults to 5 minutes; the FACTORY_API_KEY_HELPER_TTL_MS environment variable overrides it.

authMode"provider-default" | "bearer"

Credential transport for HTTP Anthropic models. Omit it or use provider-default to send the credential as x-api-key. Use bearer to send it as Authorization: Bearer. Bearer mode requires provider: "anthropic" and isn't supported for Bedrock models.

providerstringrequired

Provider type that determines API compatibility. See the Understanding Providers section below.

maxOutputTokensnumber

Maximum output tokens for model responses.

maxContextLimitnumber

The model's context window, in input tokens. Droid compacts the conversation to stay within it and won't send a message too large to fit. A larger value doesn't make Droid compact later, though: compaction still starts at your compaction token limit, 250,000 by default, as it does for built-in models with large windows. To compact later, raise Default compaction token limit or choose Add model compaction override in /settings, or set compactionTokenLimit or compactionTokenLimitPerModel in ~/.factory/settings.json. Keep that limit below maxContextLimit so the response still fits. If your provider's window counts output tokens too, set maxContextLimit to the window minus maxOutputTokens. If you omit maxContextLimit and Droid doesn't know the model's window, it assumes 250,000 tokens or your compaction token limit, whichever is higher.

reasoningEffortstring

Default reasoning effort for a model Droid doesn't recognize. Setting it also offers off, low, medium, and high in the model selector. When model or baseModelId matches a built-in model, that model's levels and default apply instead.

enableThinkingboolean

For anthropic and bedrock-converse models, set to true to turn on extended thinking.

thinkingMaxTokensnumber

Token budget for extended thinking on anthropic and bedrock-converse models.

baseModelIdstring

ID of the built-in model this entry serves (see Available Models), for example when your provider uses its own name for it. Droid applies that model's reasoning levels.

useInRouterboolean

Set to true to let Auto Model use this model in place of the built-in model named by baseModelId. Requires your organization to allow BYOK in Auto Model (modelPolicy.allowFactoryRouterByok).

noImageSupportboolean

Set to true to disable image inputs for this model. Defaults to false for anthropic, openai, and bedrock-converse models and true for generic-chat-completion-api models.

extraArgsobject

Additional provider-specific arguments to include in API requests.

extraHeadersobject

Additional HTTP headers to send with requests.

bedrockobject

AWS Bedrock routing options (authMethod, awsRegion, awsProfile, bedrockBaseUrl, awsAuthRefresh, awsCredentialExport, requestMetadata). See AWS Bedrock.

Using extraArgs

Pass provider-specific parameters like temperature or top_p by adding an extraArgs object:

JSON
{
  "extraArgs": {
    "temperature": 0.7,
    "top_p": 0.9
  }
}

Using extraHeaders

Add custom HTTP headers to API requests by adding an extraHeaders object:

JSON
{
  "extraHeaders": {
    "X-Custom-Header": "value",
    "Authorization": "Bearer YOUR_TOKEN"
  }
}

Dynamic credentials with apiKeyHelper

Some gateways issue short-lived tokens (for example, an internal LLM gateway fronted by an identity provider) rather than a long-lived static key. Instead of a static apiKey, point the model at an apiKeyHelper command whose standard output is the token. Droid runs it at request time, caches the result, and refreshes it automatically.

JSON
{
  "customModels": [
    {
      "model": "your-model",
      "displayName": "Gateway Model",
      "baseUrl": "https://llm-gateway.internal.example.com/v1",
      "provider": "generic-chat-completion-api",
      "apiKeyHelper": "/usr/local/bin/mint-gateway-token.sh",
      "apiKeyHelperTtlMs": 300000
    }
  ]
}

Behavior:

  • The command's trimmed stdout is used as the credential (sent as the provider-appropriate auth header). apiKeyHelper takes precedence over any static apiKey.
  • The command runs from your home directory, so use an absolute path or one relative to your home directory.
  • The token is cached and re-minted when it expires. TTL resolution order: the FACTORY_API_KEY_HELPER_TTL_MS environment variable, then the per-model apiKeyHelperTtlMs, then a 5-minute default.
  • The token is also refreshed once after a 401 response. If the command fails, it enters a short cooldown and fails fast rather than retrying on every request.
  • The token, command output, and command text are never logged.
Warning

apiKeyHelper runs a shell command. Droid honors it in your personal settings (~/.factory/settings.json) and org-managed settings, but strips it from project and folder settings so an untrusted repository cannot execute commands on your machine.

Note

When you add or change a custom model in org-managed settings, it cannot use a static apiKey. Use an environment variable reference such as ${PROVIDER_API_KEY}, apiKeyHelper, a keyless endpoint, or a Bedrock model instead. Existing static-key models keep working while unchanged. To change one, including rotating its key, replace the static key with one of these options.

This mirrors Claude Code's apiKeyHelper (the FACTORY_API_KEY_HELPER_TTL_MS variable is the analog of CLAUDE_CODE_API_KEY_HELPER_TTL_MS).

Anthropic bearer authentication

By default, provider: "anthropic" sends the credential from apiKey or apiKeyHelper in the x-api-key header. Some Anthropic Messages-compatible gateways require Authorization: Bearer instead. Set authMode to bearer for those gateways:

JSON
{
  "customModels": [
    {
      "model": "claude-compatible-model",
      "displayName": "Claude-compatible gateway",
      "baseUrl": "https://llm-gateway.internal.example.com/anthropic",
      "provider": "anthropic",
      "apiKeyHelper": "/usr/local/bin/mint-gateway-token.sh",
      "authMode": "bearer"
    }
  ]
}

authMode: "bearer" is only valid for non-Bedrock models using provider: "anthropic".

AWS Bedrock

To route a custom model through AWS Bedrock, add a bedrock object and set provider to match the Bedrock API: anthropic for InvokeModel on Bedrock Runtime or Messages on Bedrock Mantle, bedrock-converse for Converse, or openai for Bedrock's OpenAI-compatible endpoint. Bedrock models don't use apiKey or baseUrl.

JSON
{
  "customModels": [
    {
      "model": "anthropic.claude-sonnet-4-5-20250929-v1:0",
      "displayName": "Sonnet 4.5 [Bedrock]",
      "provider": "anthropic",
      "bedrock": {
        "authMethod": "aws-credentials",
        "awsRegion": "us-west-2",
        "awsProfile": "my-aws-profile"
      }
    }
  ]
}

authMethod selects where Droid gets credentials:

authMethodCredentials come from
aws-credentialsThe standard AWS credential chain: environment variables, ~/.aws files, SSO, or awsProfile. Add awsAuthRefresh to run a login command when no credentials are available.
credentials-commandThe required awsCredentialExport command, which runs when no valid AWS credentials are already available.
environment-api-keyA Bedrock API key in the AWS_BEARER_TOKEN_BEDROCK environment variable. Don't put the key in settings.

The awsCredentialExport command must print JSON with a top-level Credentials object containing AccessKeyId, SecretAccessKey, and, for temporary credentials, SessionToken. Shell export lines are rejected. If you omit authMethod, Droid tries AWS_BEARER_TOKEN_BEDROCK, then the AWS credential chain, then awsCredentialExport, then awsAuthRefresh.

Warning

Credentials, profiles, and commands come from the machine running Droid. An org-shared model doesn't distribute them, so set up the chosen method and region on every machine that uses the model. Like apiKeyHelper, awsAuthRefresh and awsCredentialExport are honored only in personal and org-managed settings.

Bedrock fields

FieldTypeDescription
authMethodstringaws-credentials, credentials-command, or environment-api-key. See the table above.
awsRegionstringAWS region for Bedrock requests. Optional; when omitted, Droid falls back to the AWS default region chain. Supports ${VAR_NAME} interpolation.
awsProfilestringAWS profile name to resolve credentials and (as a last resort) a region. Supports ${VAR_NAME} interpolation.
bedrockBaseUrlstringExplicit Bedrock endpoint override. Accepts a full URL or a ${VAR_NAME} reference that expands to one.
awsAuthRefreshstringShell command that refreshes AWS credentials, such as an SSO login, when none are available.
awsCredentialExportstringShell command that prints AWS credentials as Credentials JSON. Required for credentials-command.
requestMetadataobjectKey-value tags attached to every Bedrock inference call for this model, so Droid usage is attributable in Bedrock model-invocation logs and cost reports. Keys and values support ${VAR_NAME} interpolation. See Request metadata.

Request metadata

requestMetadata is a map of string keys to string values that Droid sends on every Bedrock inference call for that model. AWS surfaces the tags in model-invocation logs and cost allocation, which is how you attribute Bedrock spend in your own AWS account back to Droid, a team, or a cost center:

JSON
{
  "customModels": [
    {
      "model": "anthropic.claude-sonnet-4-5-20250929-v1:0",
      "displayName": "Sonnet 4.5 [Bedrock]",
      "provider": "anthropic",
      "bedrock": {
        "awsRegion": "us-west-2",
        "requestMetadata": {
          "tool": "droid",
          "team": "platform-eng",
          "cost_center": "${COST_CENTER}"
        }
      }
    }
  ]
}

Bedrock carries this parameter differently on each of its wire formats, so what Droid sends depends on the model's provider:

providerBedrock APIHow the tags are sent
anthropicBedrock Runtime: InvokeModel / InvokeModelWithResponseStreamAs the X-Amzn-Bedrock-Request-Metadata header, included in the SigV4 signature.
bedrock-converseConverse / ConverseStreamAs the requestMetadata field in the request body.
openaiBedrock's OpenAI-compatible endpointNot sent. AWS does not support request metadata there, so the setting has no effect.

AWS constrains the tags, and exceeding a limit fails every call on that model:

  • At most 16 entries.
  • Keys are 1-256 characters; values are at most 256 characters.
  • Keys and values may contain only alphanumerics, whitespace, and : _ @ $ # = / + , . -.

Droid checks these limits per model, at request time, on the two APIs that actually send the tags. A violation fails only that model with a message naming the offending key, rather than invalidating your whole settings file. If the block itself cannot be represented at all (not an object of string values, or two keys that collapse onto the same name after ${VAR_NAME} expansion), Droid logs a warning naming the key, drops requestMetadata for that model, and keeps the rest of your settings.

Warning

Tags are attribution data, not access control. They are sent to AWS on every call, and org-distributed values are readable by every member of the organization. Do not put secrets or credentials in requestMetadata.

Environment variable interpolation

Like apiKey, the awsRegion, awsProfile, bedrockBaseUrl, and requestMetadata fields support ${VAR_NAME} references in settings.json/settings.local.json, expanded per workspace. This lets a single centrally-distributed customModels config resolve to the correct value in each environment:

JSON
{
  "bedrock": {
    "awsRegion": "${AWS_REGION}"
  }
}

If a referenced variable is not set, Droid fails fast with a clear error naming the missing variable instead of sending the literal ${VAR_NAME} to AWS. In requestMetadata, keys are expanded alongside values, and an unresolved reference is only reported on the APIs that send the tags, so it never fails a model whose dialect ignores them.

Note

awsAuthRefresh and awsCredentialExport are not expanded by the settings parser. They run through a shell, so ${VAR} in those commands is substituted by the shell at run time against the child process environment.

Region resolution

When awsRegion is omitted, Droid resolves the region using the AWS default chain, in order:

  1. 1
    AWS_REGION
  2. 2
    AWS_DEFAULT_REGION
  3. 3
    The region set on the resolved AWS profile in ~/.aws/config

If none of these supply a region, Droid fails fast with a clear error rather than silently defaulting, so requests never target a region the workspace did not choose (important for VPC-endpoint region affinity).

Understanding providers

Factory supports four provider types that determine API compatibility:

ProviderAPI FormatUse ForDocumentation
anthropicAnthropic Messages API (v1/messages)Anthropic models on their official API or compatible proxiesAnthropic Messages API
openaiOpenAI Responses APIOpenAI models on their official API or compatible proxies. Required for the newest models like GPT-5 and GPT-5-Codex.OpenAI Responses API
generic-chat-completion-apiOpenAI Chat Completions APIOpenRouter, Fireworks, Together AI, Ollama, vLLM, and most open-source providersOpenAI Chat Completions API
bedrock-converseAWS Bedrock Converse APIModels on AWS Bedrock through the Converse API. Requires a bedrock block; see AWS BedrockBedrock Converse API
Warning

Factory is actively verifying Droid's performance on popular models, but we cannot guarantee that all custom models will work out of the box. Only Anthropic and OpenAI models accessed via their official APIs are fully tested and benchmarked.

Note

Model Size Consideration: Models below 30 billion parameters have shown significantly lower performance on agentic coding tasks. While these smaller models can be useful for experimentation and learning, they are generally not recommended for production coding work or complex software engineering tasks.

Provider reference

Every provider below works through the configuration above. Use provider: "generic-chat-completion-api" unless you are calling OpenAI's or Anthropic's official API.

ProviderbaseUrlExample model
OpenAI (official)https://api.openai.com/v1gpt-5.3-codex (set provider: "openai")
Anthropic (official)https://api.anthropic.comclaude-sonnet-4-5-20250929 (set provider: "anthropic")
OpenRouterhttps://openrouter.ai/api/v1openai/gpt-oss-20b
Fireworks AIhttps://api.fireworks.ai/inference/v1accounts/fireworks/models/deepseek-v3p1-terminus
DeepInfrahttps://api.deepinfra.com/v1/openaiQwen/Qwen3-Coder-480B-A35B-Instruct-Turbo
Groqhttps://api.groq.com/openai/v1moonshotai/kimi-k2-instruct-0905
Basetenhttps://inference.baseten.co/v1your deployed model ID
Hugging Facehttps://router.huggingface.co/v1openai/gpt-oss-120b:fireworks-ai
Google Geminihttps://generativelanguage.googleapis.com/v1beta/gemini-2.5-pro

Local models

Run models on your own hardware with an OpenAI-compatible server, then point baseUrl at it:

  • Ollama: set baseUrl to http://localhost:11434/v1 (no apiKey needed, though some builds want any placeholder value), and raise the context window to at least 32k (for example OLLAMA_CONTEXT_LENGTH=32000 ollama serve). Models with 30B+ parameters are recommended for agentic coding, and Ollama Cloud models work through the same endpoint.
  • LM Studio: set baseUrl to http://localhost:1234/v1, load an OpenAI-compatible model, and start its local server.

Prompt caching

The Droid CLI automatically uses prompt caching when available to reduce API costs:

  • Official providers (anthropic, openai): Factory attempts to use prompt caching via the official APIs. Caching behavior follows each provider's implementation and requirements.
  • Generic providers (generic-chat-completion-api): Prompt caching support varies by provider and cannot be guaranteed. Some providers may support caching, while others may not.

Verifying prompt caching

To check if prompt caching is working correctly with your custom model:

  1. 1
    Run a conversation with your custom model
  2. 2
    Use the /cost command in the Droid CLI to view cost breakdowns
  3. 3
    Look for cache hit rates and savings in the output

If you're not seeing expected caching savings, consult your provider's documentation about their prompt caching support and requirements.

Troubleshooting