# Luqira Public API Public, read-only endpoints served from `https://luqira.com`. No API key, no token, no authentication of any kind. Every catalogue endpoint is a plain `GET`, and the two agent protocol endpoints at the end of this page, A2A and MCP, are `POST`. All of them send `Access-Control-Allow-Origin: *`, so browsers and agents can call them directly from any origin. These endpoints back the public luqira.com website. They are documented here so that AI agents and other automated clients can read Luqira's public catalogue data without scraping the rendered HTML. This catalogue is also listed in machine-readable form at [`/.well-known/api-catalog`](https://luqira.com/.well-known/api-catalog) (RFC 9727, `application/linkset+json`). - Base URL: `https://luqira.com` - Response format: `application/json; charset=utf-8` unless noted - Errors: non-2xx responses return `{ "error": "" }` Please be reasonable with request volume. These are shared, unauthenticated endpoints on the marketing origin, not a metered product API. If you need bulk or high-frequency access, get in touch at https://meet.luqira.com. --- ## GET /api/avatars The public Luqira avatar library: the studio-grade avatars shown on the site. Every one is driven by Luqira's own avatar model, developed in house on Luqira's proprietary technology and rendered on Luqira's own GPUs. There is no third-party or licensed avatar engine behind this library. **Response** `200 application/json` ```json { "avatars": [ "380e432c-ffdb-4db7-9504-35bcf9d5b44f", "5e22d4af-8f42-4150-addd-f7335ee5bdea" ] } ``` `avatars` is a flat array of stable avatar identifiers (UUIDs), in no meaningful order. An id is everything you need: render its portrait by dropping it into the image proxy below. ``` https://luqira.com/api/avatars/img/{id}?w=400 ``` That proxy is the only way to fetch a portrait. There is no field carrying the full-size original — the avatar stills live on storage we do not publish. Only published, public avatars are returned. Private customer clones are never listed. --- ## GET /api/avatars/img/:id The avatar still, downscaled and re-encoded as WebP, cached on our side. This is the only way to fetch an avatar portrait: about 10KB against the roughly 1MB original, served with a 30-day immutable cache and an ETag. **Query** | Param | Type | Notes | | --- | --- | --- | | `w` | number | Target width in px. Snapped to the nearest of `200`, `400`, `800`. Defaults to `400`. Never upscales past the source. | **Response** `200 image/webp` — the image bytes. `304` when the ETag matches, `404` for an unknown avatar id. Only ids present in `GET /api/avatars` resolve; this is not a general-purpose image proxy. --- ## GET /api/voices/catalogue The voice catalogue available for avatar speech. Every voice is one of Luqira's own multilingual cloned voices, trained by Luqira and running on Luqira's GPUs. There is no third-party TTS anywhere in the catalogue. **Architecture and rights.** Luqira Voices is trained in-house on `k2-fsa/OmniVoice`, an audio-codebook TTS architecture built on `Qwen/Qwen3-0.6B`. Both upstreams are Apache-2.0. The parameters are trained from random initialisation on that architecture, so no third-party model weights are inherited: OmniVoice supplies the architecture, not a checkpoint to fine-tune. Luqira chose the corpora, built the mix, ran the training, and owns the weights it trained, with full commercial rights and no licence to renegotiate. The upstreams retain their own copyright; Luqira holds a permanent Apache-2.0 grant over them. The weights carry Luqira's own licence rather than Apache-2.0. The model is published as a model card at : 612M parameters, 24 kHz output, tagged `trained-from-scratch`, with the corpus ids and 104 language codes listed there as well. The card is public; the weights behind it are gated. The voice model's training corpora are published rather than merely asserted, so provenance can be checked against public sources. Six OpenSLR collections plus Google FLEURS: | Corpus | SLR | License | Scale | Languages | |---|---|---|---|---| | Multilingual LibriSpeech | SLR94 | CC-BY-4.0 | 50,000h | en, de, nl, fr, it, es, pt, pl | | Google high-quality TTS/ASR | SLR37, 41-44 | CC-BY-SA-4.0 | 100h @ 48kHz | bn, jv, km, npi, su | | Google crowdsourced multi-speaker speech | SLR63-66, 78-80 | CC-BY-SA-4.0 | 350h @ 48kHz | ml, mr, ta, te, gu, kn, my | | BibleTTS | SLR129 | CC-BY-SA-4.0 | 380h @ 48kHz | tw, ha, ln, yo | | AISHELL-1 | SLR33 | Apache-2.0 | 178h, 400 speakers | zh | | Yoruba multi-speaker speech data | SLR86 | CC-BY-SA-4.0 | 40h @ 48kHz | yo | | Google FLEURS | n/a | CC-BY-4.0 | ~10h per language | 102 languages | The crowdsourced collection is seven separate ids, SLR63-66 and SLR78-80, not a contiguous SLR63-80 range, and it carries no `hi` and no `pa`; both arrive via FLEURS. SLR86 is the Yoruba corpus, not the Afrikaans/Tswana/Xhosa collection (that is SLR32, unused here). More than 85 languages in the mix, trained together in a single run rather than one after another. FLEURS supplies the breadth, 102 languages at roughly ten hours each, which is what carries the run past the 25 languages the six OpenSLR collections cover on their own. Two counts appear above and they measure different things: the run gives baseline fluency in **60+ languages**, the catalogue the product sells in routes **85+**, and FLEURS itself is a **102-language** corpus. The mix therefore touches more languages than the catalogue routes. **Response** `200 application/json` ```json { "voices": [ { "DisplayName": "Mỹ Mỹ", "ShortName": "luqira:1cf3e55a-bb10-4915-8165-07fa5eac7b39", "Gender": "Female", "Locale": "vi", "LocaleName": "Vietnamese", "EditorsChoice": true } ] } ``` | Field | Type | Notes | | --- | --- | --- | | `DisplayName` | string | Voice name | | `ShortName` | string | Voice identifier, always prefixed `luqira:` | | `Gender` | string | `Female` or `Male` | | `Locale` | string | Language code, e.g. `vi`, `zh`, `yue` | | `LocaleName` | string | Human-readable language name | | `EditorsChoice` | boolean | On the editor's choice shortlist (see below) | **Query parameters** | Name | Notes | | --- | --- | | `picks` | `1`, `true` or `yes` returns only the editor's choice voices | Editor's choice is a curated shortlist — currently around 80 voices covering the main markets — for callers that want a representative sample rather than the whole catalogue. It is what the homepage voice demo loads. The picks are chosen in Luqira's admin panel and travel with the catalogue refresh, so the shortlist changes over time: count the response rather than quoting a figure back. The full catalogue is the default and is served unfiltered without `picks`; `picks=1` also falls back to the full list rather than returning an empty one. Every voice in this catalogue is one of Luqira's own models, so there is no provider or brand field to filter on — the whole list is Luqira Voice. `Locale` is the voice's primary language, not a limit: every Luqira voice is multilingual and will speak any supported language passed to the preview endpoint below. Only published, public voices are listed. Private voice clones are never returned. Voices published in the Luqira admin panel appear here within the server's refresh window. --- ## GET /api/jobs Open roles at Luqira, the same list rendered at https://luqira.com/careers. **Response** `200 application/json` ```json { "jobs": [ { "id": "ai-engineer", "title": "Senior AI Engineer, Avatar & Voice Systems", "department": "Engineering", "location": "Onsite, HCM, Vietnam", "type": "Full-time", "summary": "Our avatar and voice cloning models are already trained...", "postedAt": "2026-07-13" } ] } ``` | Field | Type | Notes | | --- | --- | --- | | `id` | string | Job slug. Use it for the endpoint below and for `/careers/{id}` | | `title` | string | Role title | | `department` | string | Owning team | | `location` | string | Work location and arrangement | | `type` | string | Employment type, e.g. `Full-time` | | `summary` | string | Short role description | | `postedAt` | string | ISO 8601 date (`YYYY-MM-DD`) | --- ## GET /api/jobs/{id}/jd The full job description for one role, as Markdown. - `id`: a job slug from `GET /api/jobs` **Response** `200 text/markdown` Returns the raw Markdown body of the job description. **Errors** | Status | Body | Cause | | --- | --- | --- | | `400` | `{ "error": "Invalid id" }` | Slug contained no usable characters | | `404` | `{ "error": "Not found" }` | No job with that slug | --- ## GET /api/status Service status for the Luqira backend and its upstream dependencies. This is the data behind https://luqira.com/status. **Response** `200 application/json`, `Cache-Control: no-store` ```json { "ok": true, "service": "Luqira AI Backend", "uptimeSeconds": 21195, "timestamp": "2026-07-17T07:00:16.767Z", "components": { "api": { "status": "up" }, "aiAgent": { "status": "up", "latencyMs": 136, "model": "qwen/qwen3-embedding-0.6b", "toolCalling": true, "monthlyTokens": 9068546 }, "meet": { "status": "up" } } } ``` | Field | Type | Notes | | --- | --- | --- | | `ok` | boolean | True when every upstream component reports `up` | | `service` | string | Always `Luqira AI Backend` | | `uptimeSeconds` | number | Seconds since this server process started | | `timestamp` | string | ISO 8601 timestamp of the probe | | `components` | object | Per-dependency status, each with `status` of `up` or `down` | The `aiAgent` component adds `model` (the agent's model id), `toolCalling` (whether the agent can invoke tools), and `monthlyTokens` (token spend attributed to the current month). `monthlyTokens` is a derived figure, not a metered one — treat it as an indicator rather than a billing number. `ok` is `false` when any component is down. The endpoint still answers `200`, so check the `ok` field rather than the HTTP status. --- ## GET /api/live The live activity counter used by the luqira.com hero badge. **Response** `200 application/json`, `Cache-Control: no-store` ```json { "ok": true, "liveSessions": 13, "activeSessions": 0 } ``` | Field | Type | Notes | | --- | --- | --- | | `ok` | boolean | True when the media upstream is reachable | | `activeSessions` | number | Live sessions reported by the media upstream | | `liveSessions` | number | The badge figure. A modelled platform baseline with `activeSessions` added on top, so it is a presentation figure and not a precise count of concurrent streams. Use `activeSessions` if you need measured data. | --- ## GET /api/health Liveness probe. **Response** `200 application/json` ```json { "ok": true } ``` --- ## POST /a2a/v1 The A2A (Agent2Agent) endpoint, speaking JSON-RPC 2.0 over HTTPS. Agent card: [`/.well-known/agent-card.json`](https://luqira.com/.well-known/agent-card.json). This is a deterministic lookup agent over the same public data the `GET` endpoints above serve. There is no language model behind it, so it cannot invent a voice, a role or a number. It answers five skills: `voice-catalogue-lookup`, `avatar-library-lookup`, `service-status`, `open-roles` and `luqira-overview`. Send either a plain text prompt or, for predictable routing, a `data` part naming the skill and its arguments. **Request** `POST /a2a/v1`, `Content-Type: application/json` ```json { "jsonrpc": "2.0", "id": 1, "method": "SendMessage", "params": { "message": { "messageId": "1f0c...", "role": "ROLE_USER", "parts": [{ "data": { "skill": "voice-catalogue-lookup", "language": "vi", "gender": "Female" } }] } } } ``` **Response** `200 application/json` The result is a `SendMessageResponse` carrying a `message`, never a `task`: every skill completes inside the request, so there is nothing to poll. Part one is a prose summary, part two is the same answer as JSON. ```json { "jsonrpc": "2.0", "id": 1, "result": { "message": { "messageId": "7a2a...", "contextId": "ae96...", "role": "ROLE_AGENT", "parts": [ { "text": "Luqira publishes 559 public voices, of which 8 match ..." }, { "data": { "matched": 8, "voices": [] }, "mediaType": "application/json" } ], "metadata": { "skillId": "voice-catalogue-lookup" } } } } ``` **Supported methods**: `SendMessage` and `ListTasks`. The card declares `streaming`, `pushNotifications` and `extendedAgentCard` all `false`, and the matching methods return the A2A error that says so rather than a stub response: `-32004` for streaming, `-32003` for push notifications, `-32007` for the extended card. `GetTask` and `CancelTask` return `-32001`, because an agent that never creates a task has no task to look up. The v0.3 method spellings (`message/send`, `tasks/get`) are still accepted. **Gotcha**: `limit` is capped at 100 and defaults to 20, so read `matched` rather than counting the returned array. --- ## POST /mcp The MCP (Model Context Protocol) endpoint, speaking Streamable HTTP. Server card: [`/mcp/server-card`](https://luqira.com/mcp/server-card), mirrored at [`/.well-known/mcp/server-card.json`](https://luqira.com/.well-known/mcp/server-card.json). It runs the same deterministic lookups as the A2A endpoint above, exposed as MCP tools. There is no language model behind it either, so it cannot invent a voice, a role or a number. Add it to any MCP client as a remote server at `https://luqira.com/mcp`. Nothing needs configuring: no key, no account, no per-tenant URL. **Protocol versions**: `2026-07-28`, `2025-11-25`, `2025-06-18` and `2025-03-26`. Clients on `2025-11-25` and earlier handshake with `initialize`; `2026-07-28` replaced that with `server/discover`. Both are answered. The server is stateless and issues no `Mcp-Session-Id`, and it opens no GET stream, so `GET /mcp` is a `405`. **Tools**: `list_voices`, `list_avatars`, `service_status`, `list_open_roles`, `luqira_overview`. All read-only. **Resources**: `llms.txt`, `llms-full.txt`, `api-docs.md` and `auth.md`, read by their public URL, plus the template `https://luqira.com/api/jobs/{id}/jd` for a single job description. **Prompts**: `choose_a_voice` (arguments `language`, optional `audience`) and `luqira_briefing`. **Request** `POST /mcp`, `Content-Type: application/json`, `Accept: application/json, text/event-stream` ```json { "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "list_voices", "arguments": { "language": "vi", "gender": "Female", "limit": 3 } } } ``` **Response** `200 application/json` ```json { "jsonrpc": "2.0", "id": 1, "result": { "content": [{ "type": "text", "text": "Luqira publishes 559 public voices, of which 8 match ..." }], "structuredContent": { "matched": 8, "returned": 3, "voices": [] }, "isError": false } } ``` **Gotcha**: the prose in `content[0]` is written to stand on its own, because `structuredContent` only reached the protocol in `2025-06-18` and older clients drop it. No tool declares an `outputSchema`, since parts of these payloads carry fields owned by the upstream voice catalogue rather than by this server. **In the browser**: every page on luqira.com registers a matching set of [WebMCP](https://webmachinelearning.github.io/webmcp/) tools on load, via `navigator.modelContext.registerTool()` and `document.modelContext`. Four of them (`luqira_find_voice`, `luqira_list_avatars`, `luqira_list_open_roles`, `luqira_service_status`) call this endpoint and return the `CallToolResult` unchanged, so a browser agent and a remote MCP client get the same answers. Two do what only the page can: `luqira_navigate` moves the tab between pages on this site, and `luqira_read_page` returns the current route as Markdown. --- **Not this server**: the Luqira product app has its own MCP server at https://meet.luqira.com/mcp, which acts inside a customer's account once they approve the connection in a browser: accounts, workspace setup, billing, and the library of scripts and products. It shares nothing with this endpoint but the protocol. It is described in [`/llms-full.txt`](https://luqira.com/llms-full.txt) under "Connect an AI Assistant", and in [`/auth.md`](https://luqira.com/auth.md). --- ## See also - MCP server card: https://luqira.com/mcp/server-card - Product app MCP server (accounts, billing, scripts, products): https://meet.luqira.com/mcp - AI catalog (every agent artifact on this origin): https://luqira.com/.well-known/ai-catalog.json - A2A agent card: https://luqira.com/.well-known/agent-card.json - Site overview for LLMs: https://luqira.com/llms.txt - Full product reference: https://luqira.com/llms-full.txt - Machine-readable API catalogue: https://luqira.com/.well-known/api-catalog - Sitemap: https://luqira.com/sitemap.xml