Official TypeScript SDK for the TextSetu translation management API. Fully typed, generated from the OpenAPI spec, and isomorphic — it runs on Node 18+ and in the browser.
npm install @textsetu/sdkimport{TextSetu}from"@textsetu/sdk";constts=newTextSetu({token: process.env.TEXTSETU_TOKEN!});const{ keys, total }=awaitts.listKeys({path: { projectId },query: {search: "checkout",limit: 50},});Responses are unwrapped for you: the API sends { success: true, data: … } on
the wire, and the SDK returns the payload directly.
Don't have a project id yet? listProjects returns every project the token can
read, so you can offer a picker instead of hard-coding one:
const{ projects }=awaitts.listProjects();// → [{ id, name, orgId, sourceLanguage, keySeparator, approvalRequired }, …]Pass a token created in the TextSetu app:
| Token | Prefix | Acts as |
|---|---|---|
| Personal access token | tsu_pat_… | you — inherits your permissions across every project you can reach |
| Project token | tsu_proj_… | a machine bound to one project |
Authorization is permission-based: every token carries an explicit allow-list
of RBAC permission keys (translation_read, translation_create, …), and each
operation declares the one it needs. A token's effective access is its underlying
authority ∩ its allow-list, so a personal access token can never exceed what you
already have. There are no coarse read/write/manage scopes.
Glossaries and translation memories are organization-level resources shared across projects, so managing them requires a personal access token. Using them — TM matches, concordance search, glossary lookup — works with either token type, because those calls are project-scoped.
The calls worth knowing about, in the order you'd use them:
// 1. Reuse what's already been translated.const{ matches }=awaitts.getTmMatches({path: { projectId },body: {sourceTexts: ["Proceed to checkout"],languageCodes: ["fr"]},});// 2. No good whole-segment match? Find prior usage of the phrase instead.const{ items }=awaitts.searchTm({path: { projectId },body: {query: "checkout",languageCodes: ["fr"]},});// 3. Respect required terminology.const{matches: terms}=awaitts.lookupGlossaryTerms({path: { projectId },body: {sourceTexts: ["Proceed to checkout"],languageCodes: ["fr"]},});// 4. Check your draft before saving it.const{ issues }=awaitts.checkGlossary({path: { projectId },body: {sourceText: "Proceed to checkout",targetText: "…",languageCode: "fr",},});getTmMatches scores whole-segment similarity, so a segment that merely
contains your phrase won't surface — that's what searchTm (concordance) is
for.
Failures throw a TextSetuApiError carrying a stable machine-readable code
(branch on this — message is human-facing and may be reworded):
import{TextSetuApiError}from"@textsetu/sdk";try{awaitts.createKey({path: { projectId },body: {key: "cart.empty"}});}catch(err){if(errinstanceofTextSetuApiError){console.error(err.code,err.status,err.message);}}newTextSetu({token: "tsu_pat_…",baseUrl: "https://textsetu.example.com/api/v1",// self-hostedfetch: customFetch,// proxy agent, tests, …});Need something the wrapper doesn't expose? ts.raw is the underlying generated
client, for interceptors and other advanced use.
src/generated/ is produced from the API's OpenAPI spec — don't edit it by
hand. Regenerate with:
pnpm generate # regenerate from the spec
pnpm generate:check # fail if the committed client is staleMethod names come from the spec's operationIds, so they're stable across path
changes.
MIT