# Crowdin API Client for JavaScript/TypeScript The `@crowdin/crowdin-api-client` library is the official JavaScript/TypeScript SDK for interacting with the [Crowdin API v2](https://developer.crowdin.com/api/v2/). It provides a fully typed, promise-based client that wraps every major resource of the Crowdin platform — including projects, source files, source strings, translations, glossaries, translation memory, tasks, reports, webhooks, and more. The library supports both the standard Crowdin cloud and Crowdin Enterprise organizations, and works in Node.js as well as browser environments (using the native Fetch API or Axios interchangeably). The client is organized as a collection of resource classes (e.g., `SourceFiles`, `Translations`, `Glossaries`, `Tasks`) that are all instantiated together via the top-level `Crowdin` constructor. Each class maps closely to an API resource group, exposing strongly-typed methods that accept typed request objects and return typed `ResponseObject` or `ResponseList` promises. Pagination, retry logic, custom HTTP clients, ETag caching, and enterprise organization routing are all built in, making the client suitable for production automation pipelines, CI/CD integrations, and backend services. --- ## Initialization ### Crowdin constructor — create and configure the API client The `Crowdin` class is the single entry point. Pass a `Credentials` object with your Personal Access Token. For Crowdin Enterprise, add the `organization` field. An optional `ClientConfig` lets you swap HTTP clients, set timeouts, and configure retry behavior. ```typescript import Crowdin, { Credentials, ClientConfig } from '@crowdin/crowdin-api-client'; // Standard Crowdin const credentials: Credentials = { token: 'YOUR_PERSONAL_ACCESS_TOKEN', }; // Crowdin Enterprise const enterpriseCredentials: Credentials = { token: 'YOUR_PERSONAL_ACCESS_TOKEN', organization: 'my-org', // resolves to https://my-org.api.crowdin.com }; // With retry and timeout configuration const config: ClientConfig = { httpClientType: 'fetch', // 'axios' (default) | 'fetch' httpRequestTimeout: 10_000, // ms retryConfig: { retries: 3, waitInterval: 1000, conditions: [ { type: 'ResponseCode', value: 429 }, // retry on rate-limit ], }, userAgent: 'my-tool/1.0.0', }; const crowdin = new Crowdin(credentials, config); // All resource modules are exposed as properties: const { projectsGroupsApi, sourceFilesApi, translationsApi, glossariesApi } = crowdin; ``` --- ## Upload Storage ### `UploadStorage` — upload files to temporary storage before attaching them to projects Every file that needs to be uploaded to Crowdin (source files, screenshots, TM, glossary) must first be placed in temporary storage. The storage entry is valid for 24 hours. The `addStorage` method automatically detects the MIME type from the file extension. ```typescript import Crowdin from '@crowdin/crowdin-api-client'; import * as fs from 'fs'; const crowdin = new Crowdin({ token: process.env.CROWDIN_TOKEN! }); const { uploadStorageApi } = crowdin; // Upload a source file from disk const fileContent = fs.readFileSync('./en.json'); const storageResponse = await uploadStorageApi.addStorage('en.json', fileContent); console.log('Storage ID:', storageResponse.data.id); // e.g. 1234 // Upload inline string content const csvContent = 'term,description\nhello,A greeting'; const csvStorage = await uploadStorageApi.addStorage('glossary.csv', csvContent, 'text/csv'); // List current storages const list = await uploadStorageApi.listStorages({ limit: 10, offset: 0 }); list.data.forEach(item => console.log(item.data.id, item.data.fileName)); // Inspect a specific storage entry const storage = await uploadStorageApi.getStorage(storageResponse.data.id); console.log('File:', storage.data.fileName); // Cleanup an explicit entry await uploadStorageApi.deleteStorage(storageResponse.data.id); ``` --- ## Projects & Groups ### `ProjectsGroups` — manage projects and (Enterprise) groups Create, retrieve, update, and delete Crowdin projects. On Crowdin Enterprise, also manage the hierarchical groups that organize projects. ```typescript import Crowdin from '@crowdin/crowdin-api-client'; const crowdin = new Crowdin({ token: process.env.CROWDIN_TOKEN! }); const { projectsGroupsApi } = crowdin; // List all projects the token has access to const projects = await projectsGroupsApi.listProjects({ limit: 25, offset: 0 }); projects.data.forEach(p => console.log(p.data.id, p.data.name)); // Create a new project const newProject = await projectsGroupsApi.addProject({ name: 'My App', sourceLanguageId: 'en', targetLanguageIds: ['de', 'fr', 'ja', 'zh-CN'], description: 'Mobile app localization', }); const projectId = newProject.data.id; console.log('Created project:', projectId); // Get project details const project = await projectsGroupsApi.getProject(projectId); console.log('Status:', project.data.translateDone); // Patch-update the project (RFC 6902 JSON Patch) await projectsGroupsApi.editProject(projectId, [ { op: 'replace', path: '/description', value: 'Updated description' }, { op: 'replace', path: '/translateDuplicates', value: 1 }, ]); // List languages of a project const langs = await projectsGroupsApi.listSupportedLanguages({ limit: 50 }); console.log('Supported languages:', langs.data.length); // Enterprise only — manage groups const group = await projectsGroupsApi.addGroup({ name: 'Product Team', parentId: 0 }); const groupProjects = await projectsGroupsApi.listProjects({ groupId: group.data.id }); ``` --- ## Source Files ### `SourceFiles` — manage branches, directories, and source files within a project Upload source files, create directory structures, manage branches for version control workflows, and update file content. ```typescript import Crowdin from '@crowdin/crowdin-api-client'; import * as fs from 'fs'; const crowdin = new Crowdin({ token: process.env.CROWDIN_TOKEN! }); const { uploadStorageApi, sourceFilesApi } = crowdin; const PROJECT_ID = 123; // 1. Upload file to storage first const storageId = (await uploadStorageApi.addStorage('en.json', fs.readFileSync('./en.json'))).data.id; // 2. Create a directory const dir = await sourceFilesApi.createDirectory(PROJECT_ID, { name: 'resources', title: 'App Resources', }); // 3. Add a source file const file = await sourceFilesApi.createFile(PROJECT_ID, { storageId, name: 'en.json', directoryId: dir.data.id, type: 'json', importOptions: { contentSegmentation: true }, exportOptions: { exportPattern: '/locales/%locale%/%original_file_name%' }, }); console.log('File ID:', file.data.id); // 4. Update file content (re-upload) const newStorageId = (await uploadStorageApi.addStorage('en.json', fs.readFileSync('./en.json'))).data.id; await sourceFilesApi.updateOrRestoreFile(PROJECT_ID, file.data.id, { storageId: newStorageId }); // 5. List files const files = await sourceFilesApi.listProjectFiles(PROJECT_ID, { directoryId: dir.data.id }); files.data.forEach(f => console.log(f.data.id, f.data.name, f.data.status)); // 6. Create a branch for a new version const branch = await sourceFilesApi.createBranch(PROJECT_ID, { name: 'v2.0' }); console.log('Branch ID:', branch.data.id); // 7. Get file revision history const revisions = await sourceFilesApi.listFileRevisions(PROJECT_ID, file.data.id, { limit: 10 }); revisions.data.forEach(r => console.log(r.data.id, r.data.createdAt)); ``` --- ## Source Strings ### `SourceStrings` — manage individual translatable strings Add, edit, delete, and query source strings directly — useful for strings-based projects or when modifying content without re-uploading entire files. ```typescript import Crowdin from '@crowdin/crowdin-api-client'; const crowdin = new Crowdin({ token: process.env.CROWDIN_TOKEN! }); const { sourceStringsApi } = crowdin; const PROJECT_ID = 123; const FILE_ID = 456; // Add a new string to a file-based project const newString = await sourceStringsApi.addString(PROJECT_ID, { text: 'Welcome to our app!', identifier: 'welcome_message', fileId: FILE_ID, context: 'Displayed on the home screen after login', maxLength: 120, labelIds: [1, 2], }); console.log('String ID:', newString.data.id); // Add a plural string to a strings-based project const pluralString = await sourceStringsApi.addString(PROJECT_ID, { text: { one: '%d item', other: '%d items' }, identifier: 'item_count', branchId: 78, isHidden: false, }); // List strings filtered by label and file const strings = await sourceStringsApi.listProjectStrings(PROJECT_ID, { fileId: FILE_ID, labelIds: '1,2', filter: 'welcome', scope: 'text', limit: 25, offset: 0, }); strings.data.forEach(s => console.log(s.data.identifier, s.data.text)); // Edit a string (JSON Patch) await sourceStringsApi.editString(PROJECT_ID, newString.data.id, [ { op: 'replace', path: '/text', value: 'Welcome to the app!' }, { op: 'replace', path: '/context', value: 'Updated context' }, ]); // Batch patch multiple strings await sourceStringsApi.stringBatchOperations(PROJECT_ID, [ { op: 'replace', path: `/${newString.data.id}/isHidden`, value: true }, ]); // Delete a string await sourceStringsApi.deleteString(PROJECT_ID, newString.data.id); ``` --- ## Translations ### `Translations` — pre-translate, build, upload, and download translation packages Trigger pre-translation via TM/MT/AI, build translation packages, download them as ZIP archives, and upload existing translations. Build and pre-translate are asynchronous — poll the status until finished. ```typescript import Crowdin from '@crowdin/crowdin-api-client'; import * as fs from 'fs'; import axios from 'axios'; const crowdin = new Crowdin({ token: process.env.CROWDIN_TOKEN! }); const { translationsApi, uploadStorageApi } = crowdin; const PROJECT_ID = 123; // 1. Pre-translate using Translation Memory const preTranslate = await translationsApi.applyPreTranslation(PROJECT_ID, { languageIds: ['de', 'fr'], fileIds: [456, 457], method: 'tm', autoApproveOption: 'perfectMatchOnly', translateUntranslatedOnly: true, }); let status = preTranslate.data; while (status.status !== 'finished') { await new Promise(r => setTimeout(r, 3000)); status = (await translationsApi.preTranslationStatus(PROJECT_ID, status.identifier)).data; console.log('Pre-translate progress:', status.progress); } // 2. Build a full translation package const build = await translationsApi.buildProject(PROJECT_ID, { targetLanguageIds: ['de', 'fr', 'ja'], skipUntranslatedStrings: false, exportApprovedOnly: true, }); let buildStatus = build.data; while (buildStatus.status !== 'finished') { await new Promise(r => setTimeout(r, 5000)); buildStatus = (await translationsApi.checkBuildStatus(PROJECT_ID, buildStatus.id)).data; console.log('Build progress:', buildStatus.progress + '%'); } // 3. Download the finished build const downloadLink = await translationsApi.downloadTranslations(PROJECT_ID, buildStatus.id); const response = await axios.get(downloadLink.data.url, { responseType: 'arraybuffer' }); fs.writeFileSync('translations.zip', response.data); // 4. Upload existing translations for a language const storageId = (await uploadStorageApi.addStorage('de.json', fs.readFileSync('./de.json'))).data.id; await translationsApi.uploadTranslation(PROJECT_ID, 'de', { storageId, fileId: 456, importEqSuggestions: false, autoApproveImported: false, }); // 5. Export a single language without building const exportLink = await translationsApi.exportProjectTranslation(PROJECT_ID, { targetLanguageId: 'de', format: 'xliff', skipUntranslatedStrings: true, }); console.log('Download URL:', exportLink.data.url); ``` --- ## String Translations ### `StringTranslations` — manage individual translation suggestions, approvals, and votes Add or retrieve translation suggestions for specific strings, approve them, remove approvals, and vote on suggestions. ```typescript import Crowdin from '@crowdin/crowdin-api-client'; const crowdin = new Crowdin({ token: process.env.CROWDIN_TOKEN! }); const { stringTranslationsApi } = crowdin; const PROJECT_ID = 123; const STRING_ID = 789; // Add a translation suggestion for a string const translation = await stringTranslationsApi.addTranslation(PROJECT_ID, { stringId: STRING_ID, languageId: 'de', text: 'Willkommen in unserer App!', addToTm: true, }); console.log('Translation ID:', translation.data.id); // List all suggestions for a string/language pair const suggestions = await stringTranslationsApi.listStringTranslations(PROJECT_ID, STRING_ID, 'de', { limit: 10, denormalizePlaceholders: 1, }); suggestions.data.forEach(s => console.log(s.data.text, 'rating:', s.data.rating)); // Approve a translation const approval = await stringTranslationsApi.addApproval(PROJECT_ID, { translationId: translation.data.id, }); console.log('Approval ID:', approval.data.id); // List approvals for a file const approvals = await stringTranslationsApi.listTranslationApprovals(PROJECT_ID, { fileId: 456, languageId: 'de', limit: 50, }); // Up-vote a translation const vote = await stringTranslationsApi.addVote(PROJECT_ID, { translationId: translation.data.id, mark: 'up', }); console.log('Vote ID:', vote.data.id); // Remove an approval await stringTranslationsApi.removeApproval(PROJECT_ID, approval.data.id); // List all translations for an entire language const langTranslations = await stringTranslationsApi.listLanguageTranslations(PROJECT_ID, 'de', { fileId: 456, approvedOnly: 1, limit: 25, }); ``` --- ## Glossaries ### `Glossaries` — manage translation glossaries and their terms Create glossaries, add or remove terms, import/export glossary files (TBX, CSV, XLSX), search for terms via concordance search, and manage multilingual concepts. ```typescript import Crowdin from '@crowdin/crowdin-api-client'; import * as fs from 'fs'; const crowdin = new Crowdin({ token: process.env.CROWDIN_TOKEN! }); const { glossariesApi, uploadStorageApi } = crowdin; // Create a glossary const glossary = await glossariesApi.addGlossary({ name: 'Product Glossary', languageId: 'en', }); const GLOSSARY_ID = glossary.data.id; console.log('Glossary ID:', GLOSSARY_ID); // Add a term const term = await glossariesApi.addTerm(GLOSSARY_ID, { languageId: 'en', text: 'Dashboard', description: 'The main overview screen of the application', partOfSpeech: 'noun', status: 'preferred', }); console.log('Term ID:', term.data.id); // Export glossary to TBX const exportJob = await glossariesApi.exportGlossary(GLOSSARY_ID, { format: 'tbx' }); let exportStatus = exportJob.data; while (exportStatus.status !== 'finished') { await new Promise(r => setTimeout(r, 2000)); exportStatus = (await glossariesApi.checkGlossaryExportStatus(GLOSSARY_ID, exportStatus.identifier)).data; } const dlLink = await glossariesApi.downloadGlossary(GLOSSARY_ID, exportStatus.identifier); console.log('Download URL:', dlLink.data.url); // Import a glossary file const storageId = (await uploadStorageApi.addStorage('glossary.csv', fs.readFileSync('./glossary.csv'))).data.id; const importJob = await glossariesApi.importGlossaryFile(GLOSSARY_ID, { storageId, firstLineContainsHeader: true, }); console.log('Import job:', importJob.data.identifier); // Concordance search — find terms appearing in a source string const results = await glossariesApi.concordanceSearch(123, { sourceLanguageId: 'en', targetLanguageId: 'de', expressions: ['Dashboard', 'Profile settings'], }); results.data.forEach(r => { console.log('Source term:', r.data.sourceTerms[0]?.text); console.log('Target term:', r.data.targetTerms[0]?.text); }); ``` --- ## Translation Memory ### `TranslationMemory` — manage TM segments and leverage existing translations Create and manage Translation Memories, import existing TMX files, export TMs, and search for matching segments via fuzzy concordance search. ```typescript import Crowdin from '@crowdin/crowdin-api-client'; import * as fs from 'fs'; const crowdin = new Crowdin({ token: process.env.CROWDIN_TOKEN! }); const { translationMemoryApi, uploadStorageApi } = crowdin; // Create a TM const tm = await translationMemoryApi.addTm({ name: 'Main Translation Memory', languageId: 'en', }); const TM_ID = tm.data.id; // Import a TMX file const storageId = (await uploadStorageApi.addStorage('memory.tmx', fs.readFileSync('./memory.tmx'))).data.id; const importJob = await translationMemoryApi.importTm(TM_ID, { storageId }); let importStatus = importJob.data; while (importStatus.status !== 'finished') { await new Promise(r => setTimeout(r, 2000)); importStatus = (await translationMemoryApi.checkImportProgress(TM_ID, importStatus.identifier)).data; console.log('Import progress:', importStatus.progress); } // Export TM to TMX const exportJob = await translationMemoryApi.exportTm(TM_ID, { format: 'tmx', sourceLanguageId: 'en' }); let exportStatus = exportJob.data; while (exportStatus.status !== 'finished') { await new Promise(r => setTimeout(r, 2000)); exportStatus = (await translationMemoryApi.checkExportProgress(TM_ID, exportStatus.identifier)).data; } const dlLink = await translationMemoryApi.downloadTm(TM_ID, exportStatus.identifier); console.log('Download URL:', dlLink.data.url); // TM concordance search const matches = await translationMemoryApi.concordanceSearch(123, { sourceLanguageId: 'en', targetLanguageId: 'de', autoSubstitution: true, minRelevant: 60, expressions: ['Save changes', 'Cancel'], }); matches.data.forEach(m => { console.log('Match:', m.data.text, '— relevance:', m.data.relevant); }); ``` --- ## Tasks ### `Tasks` — create and manage translation and proofreading tasks Assign translation or proofreading work to specific contributors with due dates, scope (files, branches, or strings), and custom vendor integrations. ```typescript import Crowdin from '@crowdin/crowdin-api-client'; const crowdin = new Crowdin({ token: process.env.CROWDIN_TOKEN! }); const { tasksApi } = crowdin; const PROJECT_ID = 123; // Create a translation task (file-based) const task = await tasksApi.addTask(PROJECT_ID, { title: 'German Translation — Q4', languageId: 'de', type: 0, // 0 = TRANSLATE, 1 = PROOFREAD fileIds: [456, 457], assignees: [ { id: 10, wordsCount: 1000 }, { id: 11, wordsCount: 2000 }, ], deadline: '2024-12-31T23:59:59+00:00', description: 'Translate UI strings for the German market release.', skipAssignedStrings: true, splitContent: true, }); console.log('Task ID:', task.data.id); // List tasks (filter by status and assignee) const activeTasks = await tasksApi.listTasks(PROJECT_ID, { status: 'in_progress', assigneeId: 10, limit: 10, }); activeTasks.data.forEach(t => console.log(t.data.title, t.data.progress.percent + '%')); // Get a task's detail const taskDetail = await tasksApi.getTask(PROJECT_ID, task.data.id); console.log('Words done:', taskDetail.data.progress.done, '/', taskDetail.data.progress.total); // Export task strings as XLIFF const exportLink = await tasksApi.exportTaskStrings(PROJECT_ID, task.data.id); console.log('Export URL:', exportLink.data.url); // Edit task — update status and deadline await tasksApi.editTask(PROJECT_ID, task.data.id, [ { op: 'replace', path: '/status', value: 'done' }, { op: 'replace', path: '/deadline', value: '2025-01-15T00:00:00+00:00' }, ]); // Add a comment to a task await tasksApi.addTaskComment(PROJECT_ID, task.data.id, { text: 'Please prioritize the onboarding strings.', timeSpent: 30, }); // List tasks assigned to the current user const myTasks = await tasksApi.listUserTasks({ status: 'todo', limit: 20 }); ``` --- ## Webhooks ### `Webhooks` — subscribe to project events via HTTP callbacks Configure HTTP endpoints to receive real-time notifications for events such as file uploads, translation completion, task updates, and more. ```typescript import Crowdin from '@crowdin/crowdin-api-client'; const crowdin = new Crowdin({ token: process.env.CROWDIN_TOKEN! }); const { webhooksApi } = crowdin; const PROJECT_ID = 123; // Create a webhook for translation events const webhook = await webhooksApi.addWebhook(PROJECT_ID, { name: 'Translation Updates', url: 'https://my-server.com/crowdin-webhook', events: [ 'file.translated', 'project.translated', 'project.approved', 'task.statusChanged', 'suggestion.approved', ], requestType: 'POST', contentType: 'application/json', isActive: true, batchingEnabled: true, headers: { 'X-Api-Key': 'secret-key', }, payload: { source: 'crowdin', environment: 'production', }, }); console.log('Webhook ID:', webhook.data.id); // List all webhooks for the project const webhooks = await webhooksApi.listWebhooks(PROJECT_ID, { limit: 10 }); webhooks.data.forEach(wh => console.log(wh.data.name, wh.data.isActive ? 'active' : 'inactive')); // Update a webhook await webhooksApi.editWebhook(PROJECT_ID, webhook.data.id, [ { op: 'replace', path: '/isActive', value: false }, { op: 'add', path: '/events/-', value: 'string.added' }, ]); // Delete a webhook await webhooksApi.deleteWebhook(PROJECT_ID, webhook.data.id); ``` --- ## Reports ### `Reports` — generate cost estimation, translation cost, and contributor reports Trigger asynchronous report generation, poll for completion, and download the result as XLSX, CSV, or JSON. Reports cover cost estimation, translation costs (post-editing), top members, contribution raw data, QA check issues, and more. ```typescript import Crowdin from '@crowdin/crowdin-api-client'; import * as fs from 'fs'; import axios from 'axios'; const crowdin = new Crowdin({ token: process.env.CROWDIN_TOKEN! }); const { reportsApi } = crowdin; const PROJECT_ID = 123; // Generate a Translation Cost (post-editing) report const reportJob = await reportsApi.generateReport(PROJECT_ID, { name: 'translation-costs-pe', schema: { unit: 'words', currency: 'USD', format: 'xlsx', baseRates: { fullTranslation: 0.10, proofread: 0.05 }, individualRates: [ { languageIds: ['de', 'fr'], userIds: [10], fullTranslation: 0.12, proofread: 0.06, }, ], netRateSchemes: { tmMatch: [ { matchType: 'perfect', price: 0.0 }, { matchType: '100', price: 0.1 }, { matchType: '99-95', price: 0.4 }, { matchType: '94-90', price: 0.7 }, { matchType: '89-80', price: 1.0 }, ], mtMatch: [], suggestionMatch: [], }, dateFrom: '2024-01-01T00:00:00+00:00', dateTo: '2024-12-31T23:59:59+00:00', groupBy: 'language', }, }); // Poll until the report is ready let status = reportJob.data; while (status.status !== 'finished') { await new Promise(r => setTimeout(r, 3000)); status = (await reportsApi.checkReportStatus(PROJECT_ID, status.identifier)).data; console.log('Report progress:', status.progress); } // Download report const dlLink = await reportsApi.downloadReport(PROJECT_ID, status.identifier); const response = await axios.get(dlLink.data.url, { responseType: 'arraybuffer' }); fs.writeFileSync('translation-costs.xlsx', response.data); // Top Members report (simpler schema) const topMembersJob = await reportsApi.generateReport(PROJECT_ID, { name: 'top-members', schema: { unit: 'words', format: 'csv', languageId: 'de', dateFrom: '2024-01-01T00:00:00+00:00', dateTo: '2024-12-31T23:59:59+00:00', }, }); ``` --- ## Error Handling ### `HttpClientError` — structured error handling for API failures All API errors are wrapped in `HttpClientError` with a typed `errors` array for field-level validation messages, and a `code` for the HTTP status. ```typescript import Crowdin, { HttpClientError } from '@crowdin/crowdin-api-client'; const crowdin = new Crowdin({ token: process.env.CROWDIN_TOKEN! }); async function createFileWithErrorHandling(projectId: number, storageId: number) { try { const file = await crowdin.sourceFilesApi.createFile(projectId, { storageId, name: 'en.json', type: 'json', }); return file.data; } catch (err) { if (err instanceof HttpClientError) { console.error('HTTP Status:', err.code); // Field-level validation errors err.errors?.forEach(e => { e.error.errors.forEach(detail => { console.error(`Field "${detail.codes[0]}" — ${detail.message}`); }); }); } else { throw err; } } } ``` --- ## Custom HTTP Client & Fetch ### Custom HTTP client injection and Fetch API mode Provide your own HTTP client (e.g., for test mocking) or switch to the native Fetch API for browser/edge environments. ```typescript import Crowdin, { HttpClient } from '@crowdin/crowdin-api-client'; // Switch to fetch (Node 18+ / browser) const fetchClient = new Crowdin( { token: 'TOKEN' }, { httpClientType: 'fetch' }, ); // Inject a fully custom HTTP client (e.g., for testing) class MockHttpClient implements HttpClient { async get(url: string): Promise { return { data: [] } as unknown as T; } async post(url: string, data: unknown): Promise { return { data } as unknown as T; } async put(url: string, data: unknown): Promise { return { data } as unknown as T; } async patch(url: string, data: unknown): Promise { return { data } as unknown as T; } async delete(url: string): Promise { return undefined as unknown as T; } async head(url: string): Promise { return undefined as unknown as T; } } const mockClient = new Crowdin( { token: 'TOKEN' }, { httpClient: new MockHttpClient() }, ); // Use in tests const result = await mockClient.projectsGroupsApi.listProjects(); ``` --- ## Summary The `@crowdin/crowdin-api-client` library serves as the backbone for automation workflows built around Crowdin: CI/CD pipelines that push new source files on every commit and pull translated builds before release, sync tools that keep in-house content management systems in sync with Crowdin projects, reporting dashboards that aggregate translation costs and contributor statistics across multiple projects, and admin scripts that manage projects, users, workflows, and glossaries at scale. All operations are typed end-to-end — from request parameters to response shapes — making it straightforward to build reliable, maintainable integrations without consulting the raw REST documentation. When integrating the client, the typical pattern is: (1) instantiate `Crowdin` once with credentials and an optional retry config; (2) upload files or content to storage before attaching them to projects; (3) trigger asynchronous operations (builds, pre-translations, report generation, export/import) and poll the corresponding status endpoints until `status === 'finished'`; (4) download artifacts via the returned `DownloadLink.url`. For event-driven architectures, webhooks eliminate the need for polling by pushing real-time notifications to your endpoints whenever key events occur in a project. The library's support for both Axios and the Fetch API, combined with injectable HTTP clients, makes it equally suitable for server-side automation, serverless functions, and browser-based admin tooling.