Skip to content

Configuration keys

Every key keryx reads, with its default and what happens when it is wrong. Keys are written here in dotted form; in YAML they nest. Where the files live and which layer wins is on the configuration page.

A key not listed here is not read. In particular, studio.port appears in some older notes and is not a configuration key — the studio's port comes from --port only.

workspace.* — where reel workspaces live

Key Type Default Meaning
workspace.root string reels Directory, relative to the project root, holding one subdirectory per reel workspace.

keryx reel new <slug> creates <workspace.root>/<slug>, and keryx reel list enumerates that directory.

workspace.root is ignored by every command that takes --workspace

Passing --workspace <slug> binds the flag into the highest-precedence configuration layer under the key workspace, which replaces the workspace mapping the files supplied. workspace.root then resolves to empty and the command falls back to the built-in reels.

So on a project that sets workspace.root, reel new and reel list use the configured directory, while reel build, reel plan, reel lock, reel prune, voice takes and every other --workspace command look under reels/ and report workspace not found. reel prune <slug> given as a positional argument is unaffected.

Until this is fixed, leave workspace.root unset and keep reel workspaces in reels/.

reel.* — frame shape

Key Type Default Meaning
reel.aspect string "W:H" 9:16 Default frame shape for a reel. The short side is always 1080 px; the long side scales and rounds to an even number.

A reel workspace can override this per reel with aspect in its workspace.yaml, and reel build --aspect overrides both. An aspect more elongated than 3:1 in either direction is rejected: aspect "5:1" is too elongated (beyond 3:1).

providers.* — selecting a backend

Each generative capability sits behind an interface, and the adapter is chosen by a scalar key. An empty or unset value selects the default; an unregistered name is an error listing what is available.

Key Default Adapters actually registered
providers.image gemini gemini
providers.voice elevenlabs elevenlabs
providers.music elevenlabs elevenlabs
providers.render ffmpeg ffmpeg, afmpeg
providers.video gemini none — video generation is not implemented, so resolving this capability always fails

Chat is selected differently, by a nested key — see providers.chat.* below.

The scalar and map forms of a providers key collide

providers.render is a scalar (the adapter name). providers.render.module is a key inside a map. One YAML key cannot be both, and neither can two layers: the higher-precedence layer's shape wins outright and the other form reads as empty.

# Rejected by the YAML parser — "mapping key render already defined".
providers:
  render: afmpeg
  render:
    module: /opt/ffmpeg-wasi.wasm

Splitting them across ~/.keryx/config.yaml and the project .keryx.yaml does not help either; whichever layer is higher supplies the shape and the other contributes nothing.

The same collision applies to providers.image versus providers.image.price_per_image and providers.image.currency, and to providers.voice versus providers.voice.price_per_1k_chars. Choosing a non-default adapter and tuning that adapter's per-unit price cannot both be expressed.

providers.render.* — the afmpeg wasm module

Read only when providers.render is not set as a scalar, for the reason above.

Key Type Default Meaning
providers.render.module string the pinned published module A host path to a .wasm, or an https:// URL. A .gz URL is decompressed on download.
providers.render.module_sha256 hex string the pinned module's hash SHA-256 of the decompressed .wasm, verified after download.
providers.render.cache_dir path OS user-cache directory Where a downloaded module is cached.

KERYX_FFMPEG_WASI is the env fallback for providers.render.module, and — because of the collision — it is the only way to override the module while also selecting providers.render: afmpeg.

With nothing set, the afmpeg backend downloads a pinned ffmpeg-wasi release (the gpl variant, verified by SHA-256), caches it, and reuses it. You do not need to fetch a module by hand.

providers.chat.* — the LLM for drafting and copy

Key Type Default Meaning
providers.chat.provider string none — see below claude, claude-local, gemini, openai or openai-compatible.
providers.chat.model string the provider's own default Model id passed to the chat client.

There is no hardwired chat provider. When providers.chat.provider is unset, keryx infers it from exactly one API key being present in the environment:

  • one of ANTHROPIC_API_KEY, GEMINI_API_KEY or OPENAI_API_KEY present → that provider is used;
  • none present → no chat provider configured — set providers.chat.provider (claude|gemini|openai) + the matching API key;
  • more than one presentmultiple chat API keys present (…) — set providers.chat.provider to choose.

That last case is the one that catches people mixing vendors. Generating images with Gemini needs GEMINI_API_KEY; if you also want chat from OpenAI you will have two keys in the environment, and inference refuses rather than guessing. Set providers.chat.provider: openai explicitly and both work — image and chat provider selection are independent keys, and using different vendors for them is supported.

providers.gemini.* and providers.elevenlabs.* — adapter settings

Key Type Default Meaning
providers.gemini.model string the adapter's Imagen → Gemini fallback chain Forces an image model id. Routed by prefix: imagen* uses the Imagen :predict endpoint, anything else :generateContent.
providers.gemini.base_url URL Google's endpoint Override the API base.
providers.elevenlabs.base_url URL ElevenLabs' endpoint Override the API base.

cover --model and portrait --model override providers.gemini.model for one run.

providers.*.price_* — cost estimates

Prices are cues shown before you spend, not invoices. They are not vendor rates and are deliberately round.

Key Type Default Priced per
providers.image.price_per_image float 0.03 image
providers.voice.price_per_1k_chars float 0.30 1000 characters of narration
providers.music.price_per_second float 0.008 second of bed
providers.chat.price_per_compose float 0.01 social composition
providers.image.currency string USD the ISO code shown against every unit, not just images

spend.* — the runaway-batch guard

Key Type Default Meaning
spend.confirm_above.amount float 10.0 Currency cap per request for flat-priced units (image, music, social).
spend.confirm_above.characters float 50000 Character cap per request for voice.

Voice is measured on the character axis and everything else on the currency axis. Zero on an axis disables that axis.

themes.* — the style catalog

Key Type Default Meaning
themes.default string none The single default theme keyword for every target (the current shape).
themes.defaults.<type> string none Per-type default keyword, <type> being article, reel or portrait. The earlier shape, still read so an unmigrated catalog resolves.
themes.<keyword> map A theme definition.

Themes are not seeded. On a fresh install there is no catalog, and the first generation asks for one: no theme configured for this type — add one with keryx theme add <keyword> --type <type>. The first theme of a type becomes that type's default.

The field-by-field shape of a theme — palette, card treatment, scrim, music gain, voice clone settings — is on the themes page.

voices.* — the speaker registry

voices.<name> defines a named narration voice, referenced from a storyboard card's voice.speaker for multi-author reels. The fields are the same as a reel theme's voice block: id, stability, similarity, style, speed, speaker_boost, model, pronunciation.

Naming a speaker that is not registered fails with voice "<name>" not found (configure it under voices.<name>).

keryx init seeds a voice called matt carrying a real ElevenLabs voice id belonging to the project's author. It is an example, not a default you want; delete it or replace it with your own.

avatars.* — the actor registry

avatars.<name> defines a recurring character that can be composited into card illustrations, selected per reel through workspace.yaml's avatars list and per card through the storyboard's avatars field.

Field Meaning
ref Character reference image, project-relative.
likeness Theme-independent description of the person.
style The avatar's own base art style, used when not restyling to a theme.
palette The avatar's own palette, folded into style use.
postamble Overrides the built-in prompt postamble (expression lock, no-text rule, 9:16 framing).

Naming an unregistered avatar fails with avatar not found: "<name>" (configure it under avatars.<name>).

As with voices, keryx init seeds an example avatar called matt describing the project's author, whose ref path will not exist in your project.

log.level — logging verbosity

Key Type Default Meaning
log.level string info Framework log level. --debug overrides it for one run and survives a config reload.

studio.* — the local web studio

Key Type Default Meaning
studio.take_count int 4 Candidate takes a studio generation produces. Clamped to 1–12; a value below 1 becomes 1, above 12 becomes 12.
studio.takes_ttl duration 3m Grace window before an unpicked in-memory candidate take is reclaimed. Accepts Go duration strings, e.g. 5m.

studio.takes_ttl only bites on an in-memory project (a RAM worktree of a remote git project). A local-disk project never prunes takes; its takes are ordinary files the workspace lifecycle owns, cleared by reel prune. The sweeper runs every 30 seconds and skips anything that is the picked slot, so "generate a second round, then go back and pick from the first" works within the window.

The studio's bind port and host come from --port and --host; there is no config key for either.

git.* — the studio's git behaviour

Key Type Default Meaning
git.commit_on_save bool true The studio commits each saved change to the project repo.
git.auto_push bool false Exposed in the studio Settings panel as "push on save". Nothing reads it — setting it has no effect.

storage.* — the media object store

Unset storage.provider means local-only, which is not an error: media stays in the workspace and the sync commands report the store as unconfigured.

Key Type Default Meaning
storage.provider string unset s3, gcs, azure, file or mem.
storage.prefix string empty Project namespace inside a shared bucket. A blank prefix drops that path segment, giving keys of the form reels/<slug>/<relpath>.

Credentials are never read from keryx config. Each backend resolves them through its own chain — the AWS default chain, Google ADC, the Azure default credential.

Key Applies to Required Meaning
storage.s3.bucket s3 yes Bucket name. Missing: storage.s3.bucket is not set.
storage.s3.region s3 no Overrides the AWS chain's region.
storage.s3.endpoint s3 no Endpoint for an S3-compatible store; setting it also switches to path-style addressing.
storage.s3.profile s3 no A named AWS profile. A profile name, not a credential.
storage.gcs.bucket gcs yes Bucket name.
storage.azure.container azure yes Container name.
storage.azure.account azure no Storage account name.
storage.file.dir file yes Local directory used as the object store.

mem keeps objects in process memory and is there for tests; nothing survives the process.

platforms.* — per-platform posting

platforms.<name>.enabled (bool, default false) gates a platform for post and auth refresh. <name> is instagram, youtube, tiktok or linkedin.

Credentials resolve environment variable → OS keychain → config key, in that order. The config fallback is plaintext; prefer the environment or the keychain. The full credential set-up per platform is in the how-to guides (Instagram, YouTube, TikTok, LinkedIn).

Non-secret platform settings:

Key Default Meaning
platforms.instagram.api_base https://graph.instagram.com/v21.0 API base URL.
platforms.instagram.app_id, .user_id, .redirect_uri unset Non-secret identifiers used by the OAuth flow.
platforms.youtube.privacy private Upload privacy status.
platforms.youtube.category_id 22 (People & Blogs) YouTube category id.
platforms.youtube.client_id, .channel_id unset Non-secret identifiers.
platforms.tiktok.api_base https://open.tiktokapis.com/v2 API base URL.
platforms.tiktok.privacy SELF_ONLY Post visibility. Stays self-only until TikTok audits the app.
platforms.tiktok.client_key, .open_id unset Non-secret identifiers.
platforms.linkedin.api_base https://api.linkedin.com API base URL.
platforms.linkedin.api_version 202606 LinkedIn versioned-API header.
platforms.linkedin.visibility PUBLIC PUBLIC or CONNECTIONS. LinkedIn has no "only me".
platforms.linkedin.client_id, .author_urn unset Non-secret identifiers.

platforms.<name>.constraints.* — overriding the composition limits

Each platform ships built-in norms used to steer composition and to gate approval. Every field can be overridden per platform, so a limit can be corrected without waiting for a release.

Key Instagram YouTube TikTok LinkedIn
constraints.text_cap 2200 5000 2200 3000
constraints.title_cap 0 (no title field) 100 0 0
constraints.links_clickable false true false true
constraints.hashtag_hint several–~10 (cap 30) a few, in the description a few, trend-led; front-load text sparing (1–3); front-load ~140

Exceeding text_cap or title_cap is a hard violation: keryx approve refuses the platform. A title on a platform with title_cap: 0, or a link on a platform with links_clickable: false, is advisory — you get instagram links are not clickable — use "link in bio" and posting still proceeds.

auth.writeback.* — where refreshed tokens are written

Key Type Default Meaning
auth.writeback.backend string local local writes rotated credentials back to ~/.keryx/accounts.yaml; gitlab writes them to a project's CI/CD variables.
auth.writeback.gitlab.project string CI_PROJECT_ID Project id or path to write variables into.
auth.writeback.gitlab.api_url URL CI_API_V4_URL, else https://gitlab.com/api/v4 GitLab API base.

The gitlab backend needs GITLAB_TOKEN (api-scoped) in the environment and refuses without it.

auth.alerts.* — expiry alerting

Key Type Default Meaning
auth.alerts.backend string none none, webhook or email.
auth.alerts.webhook.url URL unset Webhook target. ALERT_WEBHOOK_URL takes precedence.
auth.alerts.email.host string unset SMTP host. Required for email.
auth.alerts.email.port int 587 SMTP port.
auth.alerts.email.from string unset Envelope sender. Required for email.
auth.alerts.email.to list unset Recipients. Required for email.
auth.alerts.email.username string unset SMTP user. ALERT_SMTP_USERNAME takes precedence.
auth.alerts.email.password string unset SMTP password. ALERT_SMTP_PASSWORD takes precedence.

webhook without a URL fails with auth.alerts.backend=webhook but no URL — set ALERT_WEBHOOK_URL or auth.alerts.webhook.url; email missing any of host, from or to fails with auth.alerts.backend=email needs auth.alerts.email.{host,from,to}.

tls.source — the studio's certificate

Key Type Default Meaning
tls.source string localca localca provisions a browser-trusted per-machine certificate; anything else uses a self-signed one.

An unknown value is not an error — it degrades to self-signed, and HTTPS is still served so the Secure cookie holds. localca also falls back to self-signed when the CA cannot be provisioned (no elevation, no certutil).

site.base_url — the canonical post URL

Key Type Default Meaning
site.base_url URL unset Base URL of the site the reel points at.

When set, storyboard draft rewrites the closing URL card to <site.base_url>/<workspace slug> rather than trusting the host the model invented. Unset, the model's text stands.