SDK reference
Every @verbatra/sdk entry point, grouped by job, with the error model and the RunSummary anatomy.
@verbatra/sdk is the engine the verbatra command runs on. The CLI is a thin wrapper, so whatever
the command line does, you can do in code. This page catalogs the whole public surface, grouped by
the job each entry point does. For end-to-end examples, see SDK recipes.
Install
pnpm add -D @verbatra/sdk
# npm
npm install -D @verbatra/sdk
# yarn
yarn add -D @verbatra/sdkRequires Node.js >=22.14.0.
API keys and the environment
The SDK never reads, holds, or accepts an API key. The provider reads its key from the environment
(for example ANTHROPIC_API_KEY) when it is constructed. Unlike the CLI, the SDK does not load
.env files: in your own script, set the variable yourself, for example with
node --env-file=.env script.js.
Every entry point takes one input object whose first field is the validated config (from
loadConfig), except where noted. Most also accept an optional second deps argument that injects
a registry, provider builder, or file system for tests; you can ignore it in normal use.
Run translations
translate
The one-shot flow: read the source, diff each target locale against the lock baseline, send the missing and changed keys to the provider, run the placeholder and ICU integrity checks, write the locale files, and update the lock. Use it for scripts, build steps, and CI jobs.
Input: { config, cwd?, dryRun?, prune?, generatePlurals?, concurrency?, cache?, onProgress?, onLockWait?, lockAcquireTimeoutMs? }.
dryRunreads, diffs, and reports without constructing or calling the provider and without writing anything.pruneremoves orphaned keys (target keys absent from source) from the written file and the lock.generatePluralssynthesizes missing CLDR plural forms (i18next-JSON with an LLM provider only). Both default to off; when set, each overrides the matching config option for that run.concurrencyruns up to that many target locales at once (default 1, strictly serial). A value below 1 throwsCONCURRENCY_INVALID; on a live run a value above 1 withmaxTokensconfigured throwsCONCURRENCY_BUDGET_CONFLICT(a dry run is exempt).cachedefaults to true and reuses the local translation-memory cache; set it to false to bypass the cache for the run (the CLI's--no-cache).onProgressis called as the run advances (per locale start and finish, and per provider sub-batch), andonLockWaitfires while a locale's write lock is contended. Both are notification callbacks; the SDK writes no output itself.lockAcquireTimeoutMsoverrides how long a contended write lock retries before failing withLOCK_CONTENDED.maxBatchSize,maxTokens, andbudgetBehaviorare config-only; there is no per-run override.
Returns a RunSummary (see its anatomy below). Whole-run problems throw
an SdkError; a single locale failing never throws and lands as status: "failed" on that
locale's entry while the run continues.
watch
Watches the source locale file and re-runs translate on every debounced change. It fires one
initial run immediately at startup, then one run per settled change. Runs are serialized: changes
during a run collapse into a single follow-up.
Input: { config, cwd?, debounceMs?, onRun, concurrency?, cache?, onProgress?, onLockWait?, lockAcquireTimeoutMs? }.
debounceMs defaults to 300. onRun is called once per run with a WatchRunResult:
{ status: "succeeded", summary } or { status: "failed", error: { code, message } }. The SDK does
no logging; onRun is the only output. concurrency, cache, onProgress, onLockWait, and
lockAcquireTimeoutMs are passed through to every run and behave exactly as on translate (a
budgeted watch with concurrency above 1 fails each run with CONCURRENCY_BUDGET_CONFLICT).
Returns a WatchController with one method, stop(), which closes the watcher and awaits the
in-flight run. A missing source file throws SOURCE_UNREADABLE at startup; every failure after
that is surfaced through onRun and watching continues.
Inspect state without writing
None of these call a provider, write a file, or mutate the lock.
check
Reports per-locale drift as counts: missing (in source, absent from target), stale (source
changed since last translated), and upToDate. Use it as a CI gate.
Input: { config, cwd?, locales? }. Returns a CheckSummary whose inSync is true exactly when
every checked locale has nothing missing and nothing stale.
diff
The detailed sibling of check: the same computation, but it returns the key lists per locale
(missing, changed, orphaned) instead of counts. Orphaned keys are report-only and never flip
hasPendingChanges, since a default run does not prune.
Input: { config, cwd?, locales? }. Returns a DiffSummary.
keyIntegrity
Reports, for each locale's changed keys, whether the current target value still matches the
source's placeholders and whether it is valid ICU. Each entry carries key, hasPlaceholders,
matches, the missing and extra placeholder tokens on a mismatch, and icuValid. Entries
never carry a source or target string value. This is the per-key integrity report Studio surfaces.
Input: { config, cwd?, locales?, keys? }. keys narrows the check; only keys that are "changed"
for a locale are ever checked. Returns one LocaleKeyIntegrity per checked locale.
lockState
Reports the lock file's existence, version, and per-locale drift (baseline key count plus missing,
stale, and up-to-date counts). exists comes from an explicit probe, so "no lock file yet" and "an
empty but present lock file" stay distinguishable. When the file is absent the result is
{ exists: false } and nothing else is read.
Input: { config, cwd?, locales? }. Returns a LockStateResult.
loadLockFile
Reads the lock file itself (verbatra.lock.json, also exported as LOCK_FILE_NAME) and returns
its parsed shape: { version, locales }, where each locale maps keys to the source content hash
they were last translated from. A missing file degrades to an empty lock, the same first-run
behavior translate relies on; use lockState when you need to distinguish absence.
Input: { cwd? }. No config needed. Returns a LockFile.
runStatus
Reads the review-flag and token-usage snapshot the most recent non-dry-run translate or watch
run persisted to .verbatra-local/run-status.json. Never throws: a missing, corrupt, or
unrecognized file degrades to { available: false }. This file is best-effort telemetry, not a
correctness baseline.
Input: { cwd? }. No config needed. Returns { available: false } or
{ available: true, version, generatedAt, usage?, budget?, locales }.
Single-key operations
These are the seams Verbatra Studio drives; use them to build your own review tooling. All three
resolve locale and key fresh on every call and throw UNKNOWN_LOCALE or UNKNOWN_KEY when
either does not exist.
keyValue
Reads one key's current source value and, when present, its current target value for one locale. Read-only.
Input: { config, cwd?, locale, key }. Returns { source, target? }; target is absent exactly
when the key does not yet exist in that locale.
editEntry
Writes one human-typed correction for one key and locale. The candidate value passes the same placeholder and ICU gate as a provider translation before anything reaches disk; on acceptance the locale file and the lock entry for that key are updated inside the locale's write lock. Never calls a provider.
Input: { config, cwd?, locale, key, value }. Returns a two-armed result:
{ accepted: true, value }, or { accepted: false, reason: "placeholder" | "icu", value } with
nothing written.
retranslateEntry
Re-runs the provider for exactly one key and locale: a single-entry call through the same provider
registry translate uses, gated through the same integrity checks. On acceptance it writes the
locale file and lock entry for that key only.
Input: { config, cwd?, locale, key }. Returns { accepted: true, value, reviewReasons } (the
review reason codes, if any, that apply to the new value) or
{ accepted: false, reason: "placeholder" | "icu", value }. Unlike translate, a provider failure
here throws: a ProviderError from @verbatra/ai-providers with a stable code such as
RATE_LIMITED or AUTH_FAILED.
Locale-file snapshots
The building blocks of a live-refresh watcher such as Studio's: capture a locale file's state, then count what changed since.
readLocaleFileSnapshot
Reads one locale file (the source locale or any target locale) and reduces it to a content hash per key. A file that does not exist yet reads as an empty snapshot rather than throwing.
Input: { config, locale, cwd? }. Returns { locale, hashes }.
diffLocaleSnapshots
Compares two snapshots of the same file, taken at different times, and counts the keys added,
changed, and removed between them. A plain synchronous function: diffLocaleSnapshots(previous, current) returns { added, changed, removed }. Counts only, never key names.
The workbook pair
The Excel handoff for human translators; see Manual translation.
exportWorkbook
Writes the strings that still need translating (missing and changed keys per locale; add unchanged
ones with includeUnchanged) into a styled .xlsx workbook. Rows carry the same review-flag
signal a translate run computes. No provider call, no lock write.
Input: { config, cwd?, out?, locales?, includeUnchanged? }. out defaults to
DEFAULT_WORKBOOK_PATH (verbatra-translations.xlsx), exported as a constant. Returns
{ path, locales }: the absolute path written and a per-locale row count.
importWorkbook
Reads a filled workbook back into the locale files, running the same source-drift, placeholder,
and ICU checks as translate. Only accepted rows advance their lock baseline; a blank or rejected
row keeps re-exporting until it is genuinely resolved.
Input: { config, workbook, cwd?, dryRun? }. Returns the same RunSummary shape as translate
(with needsReview always empty, since no provider is involved). A sheet for a locale that is not
a configured target fails that locale with CONFIG_INVALID as data on the summary, not a throw.
Config
loadConfig
Finds, loads, and validates the project config, returning a VerbatraConfig. Options:
{ cwd?, configPath?, configOverride? }, with precedence configOverride (validate an in-memory
object) over configPath (load one explicit file) over search. The search starts at cwd and
covers verbatra.config.ts (also .js/.cjs), the .verbatrarc family (.json, .yaml,
.yml, .js, .cjs, .ts), and a "verbatra" property in package.json. A glossary given as
a file path is read and validated here, so downstream code always sees a plain record.
Throws CONFIG_NOT_FOUND when nothing is found, CONFIG_INVALID when a config is found but
invalid. See The config file for the schema.
loadConfigWithMeta
The same load, plus provenance: returns { config, source, glossary }, where source says whether
the config came from a search hit, an explicit path, or an in-memory override (with the absolute
filepath when there is one), and glossary records whether the glossary was absent, inline, or
resolved from a file. Use it when you need to display where the config came from.
defineConfig
An identity helper for authoring a typed verbatra.config.ts: it returns its argument unchanged,
and exists purely for type inference and editor autocomplete, including completion of the selected
provider's known model IDs. The model restriction is authoring-only; at runtime any non-empty
string passes validation.
verbatraConfigSchema
The zod schema loadConfig validates with, exported so your own tooling can validate a config
object the same way the SDK does. Unknown top-level keys are rejected, so a stray secret cannot
hide in the config.
scaffoldingMetadata
Read-only metadata the CLI init command derives its prompts from: providerEnv (provider id to
the environment variable its key is read from), scaffoldModels (a default scaffold model per LLM
provider), and supportedFormats (the closed set of format ids). The ScaffoldableProviderId type
covers the four hosted providers; openai-compatible is excluded because it has no single required
environment variable.
The RunSummary anatomy
translate, importWorkbook, and each successful watch run resolve to a RunSummary:
interface RunSummary {
dryRun: boolean; // true when nothing was written and no provider was called
locales: LocaleSummary[]; // one entry per target locale, in config order
succeeded: string[]; // locales whose run succeeded
failed: string[]; // locales whose run failed
usage?: UsageSummary; // summed input/output tokens; absent when no call reported usage
budget?: RunBudget; // present only when maxTokens is configured
}
interface LocaleSummary {
locale: string;
status: "succeeded" | "failed";
translated: string[]; // keys translated this run (in dry-run, keys that would be)
cacheHits: string[]; // keys served from the translation-memory cache this run
unchanged: string[]; // keys already up to date
orphaned: string[]; // target keys with no matching source key (always reported)
pruned: string[]; // orphaned keys removed this run; empty unless pruning is on
invalidIcuSource: string[]; // source keys skipped for invalid ICU
integrityMismatches: string[]; // translations withheld for a placeholder mismatch
providerFailures: string[]; // keys withheld because nothing was translated for them
budgetWithheld: string[]; // keys never sent because a "stop" budget already tripped
generated: string[]; // CLDR plural forms synthesized this run
unfilled: string[]; // import only: changed rows the translator left blank
malformedRows: { row: number; column: string }[]; // import only: rows the reader could not parse
duplicateKeys: { key: string; row: number }[]; // import only: later rows for a duplicated key
notices: LocaleNotice[]; // provider notices and SDK notices for this locale
needsReview: { key: string; reasons: string[] }[]; // accepted keys flagged for a second look
usage?: UsageSummary; // this locale's summed tokens; absent if nothing reported usage
error?: { code: string; message: string }; // present only when status is "failed"
}The parts worth knowing:
- needsReview lists accepted, written keys the review heuristics flagged, each with its reason
codes:
LENGTH_RATIO_OUTLIER,EQUALS_SOURCE,GLOSSARY_TERM_MISSED,INTEGRITY_REORDERED, andPROVIDER_DEGRADED. A review flag is advisory and never withholds a key, so a key never appears in bothneedsReviewandintegrityMismatches. See Translation safety for what each code means and Review in Studio for working the queue. - notices live per locale, never at the top level. They cover provider notices (say, a DeepL
degradation) and SDK notices with the codes
PLURAL_CATEGORIES_INCOMPLETE,SUB_BATCH_FAILED,BLANK_ROW_BASELINE_RETAINED, andBUDGET_TOKENS_EXCEEDED. - usage is
undefined, never a fabricated zero, whenever nothing in that scope reported usage: a dry run never calls a provider, and DeepL never reports tokens.RunSummary.usageis the sum over the locales. - budget appears only when
maxTokensis configured:{ maxTokens, behavior, supported, tokensUsed, exceeded }. Against a token-less provider or a dry run it is still present withsupported: false, so the guardrail is visibly inert rather than falsely tripped. - error.code on a failed locale is a preserved string (the underlying provider or adapter code,
with
LOCALE_FAILEDonly as a fallback), so do not treat it as a closed set. - cacheHits lists keys served from the translation-memory cache rather than
the provider. unfilled, malformedRows, and duplicateKeys are populated only by
importWorkbook(atranslaterun leaves them empty): a blankchangedrow, a workbook row the reader could not parse, and a duplicated key whose first occurrence won.
The error model
Whole-run failures throw an SdkError: one class, a stable code, and a secret-free message.
Branch on the code, not the message. Per-locale failures, provider notices, and integrity findings
are surfaced as data on the RunSummary, never thrown.
| Code | When |
|---|---|
CONFIG_NOT_FOUND | no config was found by search, or an explicit configPath does not exist (thrown by loadConfig) |
CONFIG_INVALID | a config was found but is unparseable or fails validation, or its glossary file could not be resolved |
UNKNOWN_FORMAT | no adapter is registered for the configured format; thrown before any file is read |
UNKNOWN_LOCALE | a requested locale is not among the configured target locales |
UNKNOWN_KEY | a requested key is not in the source resource (keyValue, editEntry, retranslateEntry) |
PROVIDER_CONSTRUCTION_FAILED | the provider could not be built; wraps the provider's own error, including a missing API key |
SOURCE_UNREADABLE | the source locale file does not exist |
SOURCE_INVALID | the source locale file could not be read or parsed; wraps the adapter's read error |
LOCK_FILE_INVALID | the lock file is present but corrupt, oversized, or at an unsupported version |
LOCK_CONTENDED | a locale's write lock could not be acquired before its timeout; the message names the lock file's path |
CONCURRENCY_INVALID | concurrency was set but is not an integer of at least 1; raised before any locale runs |
CONCURRENCY_BUDGET_CONFLICT | a live run set concurrency above 1 while maxTokens is configured; raised before any provider call (a dry run is exempt) |
LOCALE_FAILED | never thrown: the fallback code recorded on a failed locale's error |
Two exceptions to the one-error-class rule: retranslateEntry re-throws the provider's own
ProviderError when the single call fails, and runStatus never throws at all. For how these
codes map to CLI exit codes, see CI and exit codes; for symptom-first
help, see Troubleshooting.