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.
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
/settingsand open Custom models, or run/modeland 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
customModelsin~/.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:
{
"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
}
]
}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).
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.
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
modelstringrequiredModel identifier sent via API (e.g.,
claude-sonnet-4-5-20250929,gpt-5.3-codex,qwen3:4b).displayNamestringHuman-friendly name shown in model selector.
baseUrlstringAPI endpoint base URL. Required unless the model has a
bedrockblock.apiKeystringYour API key for the provider. Optional: omit it for a keyless endpoint, or use
apiKeyHelperinstead. If set, it can't be empty. Supports environment variable references; see the Tip above.apiKeyHelperstringShell command whose stdout is used as the API key, resolved at request time and refreshed on a TTL and after a
401. Takes precedence overapiKey. Honored in user and org-managed settings, but not in project or folder settings; see Dynamic credentials.apiKeyHelperTtlMsnumberRefresh interval for
apiKeyHelperoutput, in milliseconds. Defaults to 5 minutes; theFACTORY_API_KEY_HELPER_TTL_MSenvironment variable overrides it.authMode"provider-default" | "bearer"Credential transport for HTTP Anthropic models. Omit it or use
provider-defaultto send the credential asx-api-key. Usebearerto send it asAuthorization: Bearer. Bearer mode requiresprovider: "anthropic"and isn't supported for Bedrock models.providerstringrequiredProvider type that determines API compatibility. See the Understanding Providers section below.
maxOutputTokensnumberMaximum output tokens for model responses.
maxContextLimitnumberThe 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 setcompactionTokenLimitorcompactionTokenLimitPerModelin~/.factory/settings.json. Keep that limit belowmaxContextLimitso the response still fits. If your provider's window counts output tokens too, setmaxContextLimitto the window minusmaxOutputTokens. If you omitmaxContextLimitand Droid doesn't know the model's window, it assumes 250,000 tokens or your compaction token limit, whichever is higher.reasoningEffortstringDefault reasoning effort for a model Droid doesn't recognize. Setting it also offers
off,low,medium, andhighin the model selector. WhenmodelorbaseModelIdmatches a built-in model, that model's levels and default apply instead.enableThinkingbooleanFor
anthropicandbedrock-conversemodels, set totrueto turn on extended thinking.thinkingMaxTokensnumberToken budget for extended thinking on
anthropicandbedrock-conversemodels.baseModelIdstringID 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.
useInRouterbooleanSet to
trueto let Auto Model use this model in place of the built-in model named bybaseModelId. Requires your organization to allow BYOK in Auto Model (modelPolicy.allowFactoryRouterByok).noImageSupportbooleanSet to
trueto disable image inputs for this model. Defaults tofalseforanthropic,openai, andbedrock-conversemodels andtrueforgeneric-chat-completion-apimodels.extraArgsobjectAdditional provider-specific arguments to include in API requests.
extraHeadersobjectAdditional HTTP headers to send with requests.
bedrockobjectAWS 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:
{
"extraArgs": {
"temperature": 0.7,
"top_p": 0.9
}
}Using extraHeaders
Add custom HTTP headers to API requests by adding an extraHeaders object:
{
"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.
{
"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).
apiKeyHelpertakes precedence over any staticapiKey. - 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_MSenvironment variable, then the per-modelapiKeyHelperTtlMs, then a 5-minute default. - The token is also refreshed once after a
401response. 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.
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.
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:
{
"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.
{
"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:
authMethod | Credentials come from |
|---|---|
aws-credentials | The standard AWS credential chain: environment variables, ~/.aws files, SSO, or awsProfile. Add awsAuthRefresh to run a login command when no credentials are available. |
credentials-command | The required awsCredentialExport command, which runs when no valid AWS credentials are already available. |
environment-api-key | A 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.
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
| Field | Type | Description |
|---|---|---|
authMethod | string | aws-credentials, credentials-command, or environment-api-key. See the table above. |
awsRegion | string | AWS region for Bedrock requests. Optional; when omitted, Droid falls back to the AWS default region chain. Supports ${VAR_NAME} interpolation. |
awsProfile | string | AWS profile name to resolve credentials and (as a last resort) a region. Supports ${VAR_NAME} interpolation. |
bedrockBaseUrl | string | Explicit Bedrock endpoint override. Accepts a full URL or a ${VAR_NAME} reference that expands to one. |
awsAuthRefresh | string | Shell command that refreshes AWS credentials, such as an SSO login, when none are available. |
awsCredentialExport | string | Shell command that prints AWS credentials as Credentials JSON. Required for credentials-command. |
requestMetadata | object | Key-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:
{
"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:
provider | Bedrock API | How the tags are sent |
|---|---|---|
anthropic | Bedrock Runtime: InvokeModel / InvokeModelWithResponseStream | As the X-Amzn-Bedrock-Request-Metadata header, included in the SigV4 signature. |
bedrock-converse | Converse / ConverseStream | As the requestMetadata field in the request body. |
openai | Bedrock's OpenAI-compatible endpoint | Not 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.
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:
{
"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.
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
AWS_REGION - 2
AWS_DEFAULT_REGION - 3The
regionset 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:
| Provider | API Format | Use For | Documentation |
|---|---|---|---|
anthropic | Anthropic Messages API (v1/messages) | Anthropic models on their official API or compatible proxies | Anthropic Messages API |
openai | OpenAI Responses API | OpenAI 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-api | OpenAI Chat Completions API | OpenRouter, Fireworks, Together AI, Ollama, vLLM, and most open-source providers | OpenAI Chat Completions API |
bedrock-converse | AWS Bedrock Converse API | Models on AWS Bedrock through the Converse API. Requires a bedrock block; see AWS Bedrock | Bedrock Converse API |
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.
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.
| Provider | baseUrl | Example model |
|---|---|---|
| OpenAI (official) | https://api.openai.com/v1 | gpt-5.3-codex (set provider: "openai") |
| Anthropic (official) | https://api.anthropic.com | claude-sonnet-4-5-20250929 (set provider: "anthropic") |
| OpenRouter | https://openrouter.ai/api/v1 | openai/gpt-oss-20b |
| Fireworks AI | https://api.fireworks.ai/inference/v1 | accounts/fireworks/models/deepseek-v3p1-terminus |
| DeepInfra | https://api.deepinfra.com/v1/openai | Qwen/Qwen3-Coder-480B-A35B-Instruct-Turbo |
| Groq | https://api.groq.com/openai/v1 | moonshotai/kimi-k2-instruct-0905 |
| Baseten | https://inference.baseten.co/v1 | your deployed model ID |
| Hugging Face | https://router.huggingface.co/v1 | openai/gpt-oss-120b:fireworks-ai |
| Google Gemini | https://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
baseUrltohttp://localhost:11434/v1(noapiKeyneeded, though some builds want any placeholder value), and raise the context window to at least 32k (for exampleOLLAMA_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
baseUrltohttp://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:
- 1Run a conversation with your custom model
- 2Use the
/costcommand in the Droid CLI to view cost breakdowns - 3Look 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.