@withmateza/sdk
v0.1.3
Published
Framework-agnostic TypeScript client for the [Mateza Platform API](https://api.mateza.rw/docs) — translation, speech, multimodal, uploads, glossary, usage, request logs, feedback, and the supported language catalog.
Downloads
283
Readme
@withmateza/sdk
Framework-agnostic TypeScript client for the Mateza Platform API — translation, speech, multimodal, uploads, glossary, usage, request logs, feedback, and the supported language catalog.
- API reference (OpenAPI): https://api.mateza.rw/docs
- API base URL:
https://api.mateza.rw - Source-language pivot: English ⇄ Kinyarwanda (with growing direct/pivot support for Swahili, Amharic, Oromo, Tigrinya, Somali, Luganda).
Install
npm install @withmateza/sdk
pnpm add @withmateza/sdk
yarn add @withmateza/sdkRequires Node.js 18+ (or any runtime that exposes a global fetch and FormData). Inside this monorepo, depend on @withmateza/sdk via workspace:*.
What's in the box
createClient/createPublicClient— server-side and browser-safe API client factories. Both return the same surface; pick the name that matches your context.ApiError,API_ERROR_CODES,isApiErrorCode,isRetryableErrorCode— a canonical error taxonomy and a default retry table, both overridable per response.request,ClientOptions,FetchFn— low-level helpers for advanced custom transports.- Re-exported Zod schemas and TypeScript types for every response (
translateResponseSchema,speechJobResponseSchema,i18nBundleResponseSchema,*Schema, …). Lang,langValues,LANGUAGE_CATALOG,TranslationSupport— language enum and capability metadata.model-exposurehelpers describing which model variants are publicly callable.
Quick Start (server)
import { createClient } from "@withmateza/sdk";
const mateza = createClient({
baseUrl: "https://api.mateza.rw",
apiKey: process.env.MATEZA_API_KEY, // server-only secret
});
const result = await mateza.translate({
text: "Hello world",
source_lang: "en",
target_lang: "rw",
model_intent: "fast",
});
console.log(result.data.translation, result.request_id);Quick Start (browser / mobile)
import { createPublicClient } from "@withmateza/sdk";
const mateza = createPublicClient({
baseUrl: "https://api.mateza.rw",
clientKey: process.env.NEXT_PUBLIC_MATEZA_CLIENT_KEY, // browser-safe
});
const langs = await mateza.listLanguages();createPublicClient is an alias of createClient; the difference is intent. Use a clientKey from any context that ships to a user device, and reserve apiKey for trusted servers.
Auth and configuration
ClientOptions (see http.ts):
| Option | Type | Notes |
| ----------- | ----------- | --------------------------------------------------------------------------------------- |
| baseUrl | string | Required. Use https://api.mateza.rw in production. |
| apiKey | string? | Default server API key. Sent as Authorization: Bearer …. |
| clientKey | string? | Default browser client key. Sent as Authorization: Bearer …. |
| fetch | FetchFn? | Custom fetch implementation. Defaults to globalThis.fetch. |
Per-call overrides: every method that accepts auth supports api_key and client_key properties on the payload. The first non-empty value wins, falling back to the constructor defaults.
All requests send cache: "no-store" and JSON-encode the body when body is a string. Multipart bodies (FormData) are forwarded untouched so the runtime can set the boundary header.
Methods
Every method returns Promise<{ data: …, request_id: string }> (except getUsage and listRequests, which mirror the API shape directly). All responses are validated with Zod schemas before being returned, so unknown server fields are stripped at the client boundary.
The full request and response schema for every endpoint below lives at https://api.mateza.rw/docs.
Translation
translate(payload) — POST /api/v1/translate
await mateza.translate({
text: "Mwiriwe",
source_lang: "rw",
target_lang: "en",
// optional:
model_variant: "rw_en_adapter_v1",
model_intent: "fast", // "auto" | "fast" | "best_quality" | "advanced"
});If the API decides to queue the request (HTTP 202), the client transparently polls /api/v1/translate/jobs/:id until the job completes, fails, or 10 minutes elapse. The returned shape is always the synchronous TranslateResponse.
translateBatch(payload) — POST /api/v1/translate/batch
Translate up to 50 items in one call. Each item carries its own source_lang / target_lang so a batch may be heterogeneous.
translateI18n(payload) — POST /api/v1/translate/i18n
Translate an i18n-style key→value bundle and benefit from server-side caching:
const bundle = await mateza.translateI18n({
source_lang: "en",
target_lang: "rw",
namespace: "marketing-site",
messages: {
"nav.home": "Home",
"hero.title": "Translate your site instantly",
},
// optional:
model_intent: "fast",
refresh: false,
});
bundle.data.stats.bundle_hit; // true on subsequent identical calls
bundle.data.messages["nav.home"]; // "Ahabanza"Re-submitting the same bundle returns immediately (bundle_hit: true). When a bundle evolves, only changed keys are rerun through the model. This is the engine that powers @withmateza/website.
startTranslateJob(payload) / getTranslateJob(jobId) — POST /api/v1/translate/jobs, GET /api/v1/translate/jobs/:id
Explicitly enqueue a translation job and poll it yourself. Useful when you want full control over backoff, observability, or progress UI.
Speech
startSpeechTranslate(payload) — POST /api/v1/speech/translate
Three input modes are supported via discriminated unions:
// 1. URL input
await mateza.startSpeechTranslate({
media_url: "https://example.com/clip.mp4",
mode: "auto",
});
// 2. Base64 input (small clips)
await mateza.startSpeechTranslate({
audio_base64: encoded,
file_name: "demo.wav",
mime_type: "audio/wav",
});
// 3. Multipart upload (large clips, browser/Node Blob)
await mateza.startSpeechTranslate({
file: blob,
file_name: "demo.wav",
analysis_outputs: ["subtitles", "summary"],
});mode accepts "auto" | "sync" | "async". In auto / async mode the response is a pending job; pair it with waitForSpeechJob below.
getSpeechJob(jobId, apiKey?, clientKey?) — GET /api/v1/speech/jobs/:id
waitForSpeechJob(params) — long-poll helper
Polls until the job reaches completed or failed. Defaults: interval_ms: 2000, timeout_ms: 120000. Throws ApiError("UPSTREAM_TIMEOUT", 504, …) on timeout.
const final = await mateza.waitForSpeechJob({
job_id: started.data.job_id,
interval_ms: 2000,
timeout_ms: 5 * 60 * 1000,
});Multimodal
processMultimodal(payload) — POST /api/v1/multimodal
One entry point for text, audio, image, video, and document inputs. Provide either text, media_url, or audio_base64 depending on input_kind. Supports the same analysis_outputs (subtitles, ocr, summary) as the speech endpoint.
Uploads
uploadMedia(payload) — POST /api/v1/uploads
Upload a Blob (or File) for reuse across speech / multimodal jobs without re-uploading the bytes. Returns a StoredMediaResponse containing an asset_id and a fresh signed download URL.
getUploadedMedia(payload) — GET /api/v1/uploads/:asset_id
Refresh the signed download URL for a previously uploaded asset.
Feedback
createTranslationFeedback(payload) — POST /api/v1/feedback/translation
Record a corrected translation that feeds the next training refresh. domain and reviewer are required for traceability; model_variant lets you target a specific adapter.
listTranslationFeedback({ limit }) — GET /api/v1/feedback/translation
Visibility
getUsage() — GET /api/v1/usage
Aggregate request, character, and minute counters for the credential's project.
listRequests() — GET /api/v1/requests
Recent request log entries scoped to the current credential.
listGlossary() — GET /api/v1/glossary
listLanguages() — GET /api/v1/languages
Supported language catalog with per-modality coverage (translation, asr, tts, vision).
guide({ topic, api_key? }) — POST /api/v1/guide
Optional, unauthenticated Rwanda visitor guide endpoint. Provides curated phrases, recommendations, and cost hints for a topic.
Errors
Every non-2xx response is normalized into ApiError:
import { ApiError, isRetryableErrorCode } from "@withmateza/sdk";
try {
await mateza.translate({ text: "…", source_lang: "en", target_lang: "rw" });
} catch (err) {
if (err instanceof ApiError) {
err.code; // "RATE_LIMITED" | "VALIDATION_ERROR" | …
err.statusCode; // 429
err.requestId; // server-issued correlation id
err.retryable; // server hint; falls back to default table
err.details; // structured payload (e.g. validation issues)
if (err.retryable || isRetryableErrorCode(err.code)) {
// back off and retry
}
}
}Canonical codes (see errors.ts):
UNAUTHORIZED, FORBIDDEN, NOT_FOUND, VALIDATION_ERROR, RATE_LIMITED, CONFLICT, IDEMPOTENCY_REPLAY, UPSTREAM_TIMEOUT, UPSTREAM_UNAVAILABLE, UNSUPPORTED_MEDIA, MEDIA_TOO_LONG, MEDIA_FETCH_FAILED, ASR_FAILED, SPEECH_FAILED, TRANSLATION_FAILED, JOB_NOT_FOUND, JOB_FAILED, PAYMENT_REQUIRED, INTERNAL_ERROR, UNKNOWN.
Default retryable codes: RATE_LIMITED, UPSTREAM_TIMEOUT, UPSTREAM_UNAVAILABLE, MEDIA_FETCH_FAILED, ASR_FAILED. The server may override retryable per response.
Languages
import { LANGUAGE_CATALOG, langValues, type Lang } from "@withmateza/sdk";
langValues; // ["en","rw","sw","am","om","ti","so","lg","sid","wal"]
const directLanguages = LANGUAGE_CATALOG.filter((entry) => entry.translation === "direct");LANGUAGE_CATALOG is the offline mirror of GET /api/v1/languages and is safe to bundle in a static UI. Use listLanguages() when you need live capability flags.
Schemas and types
All response schemas are exported as Zod schemas plus inferred TypeScript types — for example translateResponseSchema / TranslateResponse, speechJobResponseSchema / SpeechJobResponse, i18nBundleResponseSchema / I18nBundleResponse, usageSchema / Usage, languageCatalogResponseSchema / LanguageCatalogResponse. Import them when you need to validate cached payloads or reuse the input shapes:
import {
type Lang,
type TranslateResponse,
i18nBundleResponseSchema,
} from "@withmateza/sdk";
const cached = i18nBundleResponseSchema.parse(JSON.parse(disk));Custom transport
Inject a custom fetch to add observability, circuit breaking, or to run inside a non-standard runtime:
const mateza = createClient({
baseUrl: "https://api.mateza.rw",
apiKey: process.env.MATEZA_API_KEY,
fetch: async (input, init) => {
const response = await traced(input, init);
return response;
},
});Lower-level access is available via request(baseUrl, path, init, fetchFn) if you need to call an endpoint that isn't yet exposed by a high-level method.
Common mistakes
- Passing a server
apiKeyinto browser code. UseclientKeyinstead. - Calling
translatefrom a browser without settingclientKey. - Forgetting
baseUrl(the constructor throws as soon as the first request fires). - Hand-escaping placeholders (
{name},%s, URLs, HTML). The API protects them automatically. - Importing from internal source files (
@withmateza/sdk/src/...). Always import from the package entry point. - Treating responses as untyped JSON. Every method returns a Zod-validated, fully typed shape.
Related packages
@withmateza/client— framework-labeled wrappers (createReactClient,createVueClient,createReactNativeClient).@withmateza/website— DOM translator built ontranslateI18n.
Reference
- API documentation: https://api.mateza.rw/docs
- API base URL: https://api.mateza.rw
- Getting started:
docs/package-getting-started.md
