Denne integrationsguide er kun tilgængelig på engelsk.
Reference
API and MCP reference
Fetch, push and keep translations current — from CI, a runtime, a browser or an AI assistant.
Every project has a UUID and its own API keys. Build-time integrations
fetch a namespace per language with one GET; runtimes report missing keys
as they meet them; CI declares the full key set with one PUT. The Embed
script uses the unauthenticated endpoints from the visitor's browser, and
an AI assistant reaches the same project over MCP.
The same description as a file:
openapi.yml
— OpenAPI 3.1, for Postman, code generators and agents. Every endpoint below has a
Try it
panel: the request leaves your browser for this site's own host with the token you paste, and nothing is stored.
Tokens and headers
Three tokens open the API. A project API key comes from a project's Connect page and is either read-only — it may fetch and nothing else — or read/write. A personal access token comes from your avatar → Personal access tokens and attributes every write to you; one token covers one project (pat_…) or the whole workspace (pat_ws_…). The API answers on https://app.bemywords.org; the marketing host serves it too, which is what the Try-it panels below use.
Authorization: Token token=YOUR_API_KEY # project API key
Authorization: Bearer pat_… # personal access token
Ship a read-only key anywhere someone else can read it: a JavaScript bundle, a mobile app, a public repository. A read/write key in a browser lets anyone who views the source rewrite the strings that browser renders. A write attempted with a read-only key answers 403 with {"error":"read_only_key"}; a request without a valid token for the project answers 401 with {"error":"unauthorized"}.
Values are text and markup — links, <strong>, placeholders like %{name} — and what a browser would execute is refused with 422 invalid_translation: a value starting with a script URL, a link or image pointing at one, an inline event handler. That is a backstop, not a substitute for escaping where you render.
Translations
Project-scoped reads and writes. Authenticate with a project API key (Authorization: Token token=…) or a personal access token (Authorization: Bearer pat_…). A read-only key may only reach the two GETs; every write answers 403 read_only_key to it. Reads are not rate-limited; writes are capped at 300 a minute per project.
The flat { key: value } map for one namespace and language, served
from a cache that writes refresh. Responses carry an ETag; send
If-None-Match on repeat fetches and an unchanged body answers
304 Not Modified with no content. Cache the file in your build —
a build only sees what existed when it ran.
Auth:
Project API key or Personal access token
Parameter
In
What it is
project_idrequired
path
The project UUID, shown on the project's page and in its URL.
languagerequired
path
A language code the project has on — `en`, `nb`, `sv`, `pt-BR`.
namespace_keyrequired
path
The namespace's short key — `common`, `marketing`, `app`.
Responses
200
The namespace.
{
"greeting": "Hei",
"btn.save": "Lagre"
}
304
Your cached copy is still current.
401
Missing or wrong token
404
Unknown namespace
curl
curl -X GET \
-H "Authorization: Token token=YOUR_API_KEY" \
https://app.bemywords.org/api/PROJECT_ID/latest/nb/common
Adds keys the namespace does not have yet and leaves every existing
translation untouched. The runtime safety net: when your i18n library
meets a key it cannot find, post it with its source value and it
appears for translation without waiting for the next build.
Non-destructive, so safe to call from anywhere with a read/write key
you control. Throttled with the other writes at 300 a minute per project.
Auth:
Project API key or Personal access token — read/write
Parameter
In
What it is
project_idrequired
path
The project UUID, shown on the project's page and in its URL.
languagerequired
path
A language code the project has on — `en`, `nb`, `sv`, `pt-BR`.
namespace_keyrequired
path
The namespace's short key — `common`, `marketing`, `app`.
Request body
{
"welcome.title": "Welcome to BeMyWords",
"welcome.cta": "Get started"
}
Responses
201
Stored. Empty body.
403
The key is read-only.
413
Body over 5 MB — chunk it.
422
A value was refused — it starts with a script URL or carries an inline event handler.
{
"error": "invalid_translation",
"message": "Translation value starts with a script URL"
}
curl
curl -X POST \
-H "Authorization: Token token=YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"welcome.title":"Welcome to BeMyWords","welcome.cta":"Get started"}' \
https://app.bemywords.org/api/missing/PROJECT_ID/latest/nb/common
Declares every key that exists in your source for this namespace. Keys
in the payload are created or unhidden; keys the server has that you
did not send are marked hidden so translators stop seeing them — only
when language is the project's source language, and nothing is ever
deleted. A first push to an unknown namespace creates it.
preserve_source_values (default true) decides who owns an edit to
an existing source value: with the default, edits made in BeMyWords
survive your syncs and only new keys take their value from the
payload. Send false when the code is canonical — changed values are
overwritten and listed in source_value_changed, and per-language
locks on those keys clear so translators re-review.
dynamic_key_prefixes protects keys generated at runtime (error
codes, for instance) from being hidden. partial: true marks one
chunk of a larger file — nothing is hidden — for namespaces of about
a thousand new keys and more, since a request has a 25-second
budget; finish with one full sync. A flat { key: value } body is
also accepted.
Auth:
Project API key or Personal access token — read/write
Parameter
In
What it is
project_idrequired
path
The project UUID, shown on the project's page and in its URL.
languagerequired
path
A language code the project has on — `en`, `nb`, `sv`, `pt-BR`.
namespace_keyrequired
path
The namespace's short key — `common`, `marketing`, `app`.
Force-sets the value of keys that already exist in this language.
Keys it does not know come back in not_found and nothing is created
— use missing or sync for that. refreshed lists keys whose value you
sent unchanged while the source had moved: the row is stamped current
so /api/stale stops flagging it, which is how you say "still
correct for the new source" without rewriting it.
Auth:
Project API key or Personal access token — read/write
Parameter
In
What it is
project_idrequired
path
The project UUID, shown on the project's page and in its URL.
languagerequired
path
A language code the project has on — `en`, `nb`, `sv`, `pt-BR`.
namespace_keyrequired
path
The namespace's short key — `common`, `marketing`, `app`.
Keys whose translation in lang was made against an older source
value than the current one. Rows that predate source versioning are
included unless you pass include_null=false. Close the loop with
overwrite (a human value) or retranslate (AI).
Auth:
Project API key or Personal access token
Parameter
In
What it is
project_idrequired
path
The project UUID, shown on the project's page and in its URL.
namespace_keyrequired
path
The namespace's short key — `common`, `marketing`, `app`.
langrequired
query
The target language to check.
include_null
query
Set `false` to skip rows with no recorded source version.
Regenerates the translations of the listed keys in language with
the project's configured model and rules, and stores the result. Ten
keys per call — the model runs inside the request — so chunk larger
sets. Sixty calls a minute per project. Spend shows on Usage like any
other AI work; on a BYOK workspace it runs on your key.
Auth:
Project API key or Personal access token — read/write
Parameter
In
What it is
project_idrequired
path
The project UUID, shown on the project's page and in its URL.
languagerequired
path
A language code the project has on — `en`, `nb`, `sv`, `pt-BR`.
namespace_keyrequired
path
The namespace's short key — `common`, `marketing`, `app`.
For sites where the source text is the key — hosted builders and the
Embed script. Creates a source row per string that is new to the
namespace, key equal to value, and never touches an existing row.
Creates the namespace on first use. Sixty calls a minute per project.
Auth:
Project API key or Personal access token — read/write
Parameter
In
What it is
project_idrequired
path
The project UUID, shown on the project's page and in its URL.
namespace_keyrequired
path
The namespace's short key — `common`, `marketing`, `app`.
Request body
{
"source_strings": [
"Book a demo",
"Read the guide"
]
}
curl -X POST \
-H "Authorization: Token token=YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"source_strings":["Book a demo","Read the guide"]}' \
https://app.bemywords.org/api/PROJECT_ID/sync_page_strings/common
Try it
POST/api/{project_id}/redeploy
Refresh the cache and dispatch the project's redeploy
Refreshes the project's translation cache, then dispatches the GitHub
Actions workflow configured under Connect → Redeploy, so a build-time
site rebuilds with fresh strings. An audited human action: personal
access tokens only, a project key answers 403 pat_required. Every
attempt is listed under Recent redeploys on the Connect page.
Auth:
Personal access token
Parameter
In
What it is
project_idrequired
path
The project UUID, shown on the project's page and in its URL.
GitHub is not configured for the project, or its key is bad.
{
"ok": false,
"error": "not_configured"
}
502
GitHub refused the dispatch.
curl
curl -X POST \
-H "Authorization: Bearer pat_…" \
https://app.bemywords.org/api/PROJECT_ID/redeploy
Try it
Account
Who am I — for personal access tokens.
GET/api/me
The token's owner and what it reaches
A project token answers with its one project; a workspace token
(pat_ws_…) answers with the workspace and every active project in
it. The cheapest way to check a token before wiring it anywhere.
curl -X GET \
-H "Authorization: Bearer pat_…" \
https://app.bemywords.org/api/me
Try it
MCP
The Model Context Protocol endpoints — JSON-RPC 2.0 over HTTP, stateless, one POST per call. The full tool list is generated further down this page.
POST/api/mcp
MCP — the whole workspace
Workspace tokens only (pat_ws_…). JSON-RPC 2.0 in the body, one
call per POST, Accept: application/json. No session is minted —
every request stands alone, which is what lets it run on many dynos;
tools/list and tools/call work without a prior initialize.
Tools name a project per call. Send the header
X-BeMyWords-Read-Only: 1 and only the read tools are offered.
Any token for the project: an API key or a personal access token. The
same protocol and tools as the workspace endpoint, with the project
fixed, so a builder connected here never names one. A read-only API
key is offered read tools only, whatever the client declares.
Auth:
Project API key or Personal access token
Parameter
In
What it is
project_idrequired
path
The project UUID, shown on the project's page and in its URL.
The unauthenticated endpoints the Embed script calls from a visitor's browser. CORS-open, throttled per IP, guarded by the project's allowed origins where a project is named.
POST/api/embed/resolve
Host → project (the first-load handshake)
Called once by the Embed script with the workspace's public token and
the page's host. Answers the project for that host, creating it on
first sight — the zero-config install. Thirty calls a minute per IP.
Answers 503 while the emergency switch is off.
Auth:
No token — called from the visitor's browser
The script posts text it found on the page and could not translate —
up to fifty strings a call, two to five thousand characters each —
and they become source rows, key equal to value. Always answers 204,
also when the origin is not permitted or the project is not in Embed
mode; nothing about the outcome is disclosed to a page. Sixty calls
a minute per IP.
Auth:
No token — called from the visitor's browser
Parameter
In
What it is
project_idrequired
path
The project UUID, shown on the project's page and in its URL.
Request body
{
"strings": [
"Book a demo",
"Read the guide"
]
}
Responses
204
Taken.
curl
curl -X POST \
-H "Content-Type: application/json" \
-d '{"strings":["Book a demo","Read the guide"]}' \
https://app.bemywords.org/api/embed/PROJECT_ID/harvest
A flat { text_on_the_page: text_it_should_say } map for one
namespace and language, read by the script on every page view and
swapped into the DOM. Two kinds of entry, and which you get depends
on src:
source → current — for a page still showing the text as
authored.
prior → current — for a page whose translations were baked
into its build before someone corrected them. Present only while
the project has keep my site up to date between deploys on.
A regional tag falls back to its base language (en-GB → en).
Carries an ETag that varies with src; unchanged answers 304.
Refused with 403 from an origin the project has not allowed. Counts
as visitor demand for the language. 240 calls a minute per IP.
Auth:
No token — called from the visitor's browser
Parameter
In
What it is
project_idrequired
path
The project UUID, shown on the project's page and in its URL.
languagerequired
path
A language code the project has on — `en`, `nb`, `sv`, `pt-BR`.
namespace_keyrequired
path
The namespace's short key — `common`, `marketing`, `app`.
src
query
`0` asks for the prior → current entries only. Send it when the
page's translations came from its own build, so the swap
corrects stale strings without rewriting text from source. Omit
it for the full map — which is what every embed built before
this parameter existed does.
Responses
200
The dictionary.
{
"Book a demo": "Bestill en demo",
"Read the guide": "Les guiden"
}
304
Unchanged.
403
Origin not allowed.
404
Unknown project
curl
curl -X GET \
https://app.bemywords.org/api/PROJECT_ID/dictionary/nb/common
Translations that changed after since (ISO 8601, at most seven days
back), each with the prior values that are safe to swap for the
current one. Used by the editor and by ?bw-live previews. A
visitor's page never calls it: the dictionary carries recent prior
values, so one fetch both translates the page and corrects anything
stale on it. At most 500 changes; truncated says when there were
more. 120 calls a minute per IP.
Auth:
No token — called from the visitor's browser
Parameter
In
What it is
project_idrequired
path
The project UUID, shown on the project's page and in its URL.
languagerequired
path
A language code the project has on — `en`, `nb`, `sv`, `pt-BR`.
namespace_keyrequired
path
The namespace's short key — `common`, `marketing`, `app`.
curl -X GET \
https://app.bemywords.org/api/PROJECT_ID/changes/nb/common?since=2026-09-12T06:00:00Z
Try it
POST/api/{project_id}/install_ping
We see your site
One ping per browser session so the dashboard can show that the
script is live, kept seven days, no analytics. Sixty a minute per IP.
Auth:
No token — called from the visitor's browser
Parameter
In
What it is
project_idrequired
path
The project UUID, shown on the project's page and in its URL.
Responses
204
Noted.
404
Unknown project.
curl
curl -X POST \
https://app.bemywords.org/api/PROJECT_ID/install_ping
Try it
GET/api/{project_id}/public_info
Languages for the switcher
The project's source language, the languages live for visitors, and
whether the footer switcher shows — what the script needs to draw the
dropdown without baking a list into the tag. Cached five minutes.
120 calls a minute per IP.
Auth:
No token — called from the visitor's browser
Parameter
In
What it is
project_idrequired
path
The project UUID, shown on the project's page and in its URL.
curl -X GET \
https://app.bemywords.org/api/PROJECT_ID/public_info
Try it
MCP tools
What the server offers a connected assistant, generated from the tools it serves. Connect by URL with a personal access token as Bearer — the
MCP guide
has the client-by-client steps and the prompt to paste into an app builder. Every connection receives these standing instructions:
BeMyWords keeps a project's user-facing text translated. Recommended loop for an app or site:
1. get_project_status — source language, languages on, namespace keys. Turn on any language the user asked for with enable_language BEFORE registering strings.
2. Register every user-facing string you write with register_source_strings (namespace "app", keys like section.element). Never write your own translations; respect list_terminology.
3. Ship translations the simplest way: get_embed_snippet returns one <script> tag — paste it into the app's HTML head and every page is translated in the visitor's browser, with a language switcher. No files, no i18n library, nothing to re-fetch.
Only if the user wants the translations as files inside their build: wait until get_project_status shows missing = 0 per language, then get_translations per language into the app's locale files. Translation is asynchronous — never fetch before it is finished.
Read tools
list_projects
List the workspace's projects with the ids the other tools' project_id argument expects.
get_project_status
Project overview: source language, enabled languages, namespaces, and per-language counts of translated, missing and source-drifted strings. Call this first to learn the namespace keys and language codes the other tools expect.
project_id
Project id (see list_projects). Required on the workspace endpoint; omit when connected to a project endpoint.
get_embed_snippet
The simplest way to ship translations: returns one <script> tag for this project. Paste it into the app's HTML <head> (index.html) and every page is translated in the visitor's browser as soon as strings are registered — a footer language switcher included. No translation files, no i18n library, nothing to re-fetch when text changes. Prefer this over get_translations unless the user asks for translation files in their build.
project_id
Project id (see list_projects). Required on the workspace endpoint; omit when connected to a project endpoint.
get_translations
Fetch one language's translations as an i18n file (json, yml, po or strings) to write into the project, e.g. src/locales/<language>.json — for a user who wants the translations inside their build rather than the get_embed_snippet script. Translation is asynchronous: check get_project_status shows missing = 0 for the language first, or the file will be incomplete. Includes approved translations and AI drafts.
project_id
Project id (see list_projects). Required on the workspace endpoint; omit when connected to a project endpoint.
languagerequired
Language code, e.g. 'nb'. Must be enabled on the project — see get_project_status.
format
File format. Default json.
namespace_key
Limit to one namespace. Default: the whole project.
get_translation
One translation key across all languages — values, statuses, flags and comments.
project_id
Project id (see list_projects). Required on the workspace endpoint; omit when connected to a project endpoint.
namespace_keyrequired
Namespace the key lives in.
translation_keyrequired
The key, e.g. 'hero.title'.
search_translations
Search a project's translations by key or value substring, language and status. Returns at most 50 rows; pass the returned next_offset to page.
project_id
Project id (see list_projects). Required on the workspace endpoint; omit when connected to a project endpoint.
key_contains
Substring of the translation key.
value_contains
Substring of the translated text.
language
Limit to one language code.
status
Limit to one status.
offset
Paging offset from a previous call's next_offset.
list_terminology
Glossary, do-not-translate terms and substitution rules for this project. Respect these in any text you write or translate: glossary pairs are fixed translations, do-not-translate terms must appear verbatim in every language.
project_id
Project id (see list_projects). Required on the workspace endpoint; omit when connected to a project endpoint.
locale
Limit glossary and substitution rules to one language (rules marked 'all' are always included).
get_checkup_report
Findings from the most recent completed AI checkup or language audit (or one named by report_id).
project_id
Project id (see list_projects). Required on the workspace endpoint; omit when connected to a project endpoint.
report_id
A specific report's id. Default: the newest completed one.
Write tools
A read-only key, or the header X-BeMyWords-Read-Only: 1, hides these. Everything a write tool changes is attributed to the token's owner and lands as needs review.
enable_language
Turn on a target language for the project. Enable the languages FIRST: strings registered afterwards are translated into every enabled language automatically, while strings that already exist need ai_translate_keys or the app's Translate action to fill the new language. No plan limits how many languages a project has.
project_id
Project id (see list_projects). Required on the workspace endpoint; omit when connected to a project endpoint.
languagerequired
Language code to enable, e.g. 'es'. Must be a language BeMyWords supports, and not the project's source language.
register_source_strings
Register source-language strings as they are written. Creates missing keys only — an existing key's value is never changed. Translation into the project's languages starts automatically. Call this whenever new user-facing text is added.
project_id
Project id (see list_projects). Required on the workspace endpoint; omit when connected to a project endpoint.
namespace_keyrequired
Namespace to file the strings under, e.g. 'app' or 'marketing'. Created if new.
stringsrequired
Map of translation key to source-language text, e.g. {"hero.title": "Welcome"}.
update_translation
Set one translation's text. Lands as needs_review for a human to approve unless status 'approved' is explicitly requested. Attributed to the token's user.
project_id
Project id (see list_projects). Required on the workspace endpoint; omit when connected to a project endpoint.
namespace_keyrequired
translation_keyrequired
languagerequired
Language of the value being written.
valuerequired
The new text.
status
Default needs_review.
set_translation_status
Mark one translation approved or needs_review without changing its text.
project_id
Project id (see list_projects). Required on the workspace endpoint; omit when connected to a project endpoint.
namespace_keyrequired
translation_keyrequired
languagerequired
statusrequired
add_key
Create one new key with its source-language text. Fails if the key exists — an existing value is never changed. Translation starts automatically.
project_id
Project id (see list_projects). Required on the workspace endpoint; omit when connected to a project endpoint.
namespace_keyrequired
translation_keyrequired
source_valuerequired
The source-language text.
add_comment
Comment on one translation (e.g. why a wording was chosen, or a question for the reviewer).
project_id
Project id (see list_projects). Required on the workspace endpoint; omit when connected to a project endpoint.
namespace_keyrequired
translation_keyrequired
languagerequired
Which language's row to comment on.
bodyrequired
flag_translation
Set do_not_translate (term must stay verbatim) or language_locked (this language's value must not be overwritten) on one row.
project_id
Project id (see list_projects). Required on the workspace endpoint; omit when connected to a project endpoint.
namespace_keyrequired
translation_keyrequired
languagerequired
flagrequired
valuerequired
true to set, false to clear.
add_glossary_term
Add a glossary pair: whenever source_term appears, translations must use target_term. Applies to one locale, or 'all'.
project_id
Project id (see list_projects). Required on the workspace endpoint; omit when connected to a project endpoint.
source_termrequired
target_termrequired
locale
Language code, or 'all' (default).
ai_translate_keys
AI-translate up to 10 named keys into one language. Approved translations are never overwritten. For whole-language coverage, the app's own Translate action is the right tool — this is for a handful of keys.
project_id
Project id (see list_projects). Required on the workspace endpoint; omit when connected to a project endpoint.
namespace_keyrequired
translation_keysrequired
Keys to translate.
target_langrequired
Webhooks
Outbound — a translation was approved, created or updated
Configure an endpoint under Connect → Webhooks. Each delivery is a
POST with X-Webhook-Secret carrying your shared secret verbatim —
compare it before trusting the body. At-least-once: a failed delivery
is retried three times with backoff, the last outcome per webhook
shows on the Connect page, endpoints get ten seconds, and private or
loopback addresses are refused. Events: translation.approved,
translation.created, translation.updated.
Taken — the Embed endpoints that report nothing back.
304 Not Modified
Your If-None-Match still matches; reuse your copy.
400 Bad Request
A required query parameter is missing or unparsable.
401 Unauthorized
No token, a wrong one, or one for another project.
403 Forbidden
A read-only key on a write, a project key where a personal token is required, or an origin the project has not allowed.
404 Not Found
Unknown project, namespace, or a language the project does not have on.
413 Payload Too Large
Body over 5 MB — chunk it; sync accepts partial: true.
422 Unprocessable
A value was refused or a row failed to save; the body says which.
429 Too Many Requests
Writes are capped at 300 a minute per project, retranslate at 60; the Embed endpoints per IP as listed above.
Hvad koster BeMyWords?
Gratis at starte: 5.000 ord gemt, 1 websted, intet kort. Betalte planer starter ved 199 kr om måneden — 1.990 kr for et år — og tælles på gemte ord — kilden plus hver oversættelse af den — aldrig på brugere, sidevisninger eller en grænse for hvor mange sprog du kører. Medbring din egen AI-nøgle og det er 199 kr om måneden fast uden ordgrænse.
Hvilket niveau det er afhænger af hvor mange ord dit websted indeholder. Indsæt din adresse, så læser vi den og fortæller dig det.