# Genpio Public API

Public, read-only endpoints served from `https://genpio.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 genpio.com website. They are documented here so
that AI agents and other automated clients can read Genpio's public catalogue
data without scraping the rendered HTML.

This catalogue is also listed in machine-readable form at
[`/.well-known/api-catalog`](https://genpio.com/.well-known/api-catalog)
(RFC 9727, `application/linkset+json`).

- Base URL: `https://genpio.com`
- Response format: `application/json; charset=utf-8` unless noted
- Errors: non-2xx responses return `{ "error": "<message>" }`

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.genpio.com.

---

## GET /api/avatars

The public Genpio avatar library: the studio-grade avatars shown on the site.
Every one is driven by Genpio's own avatar model, developed in house on Genpio's
proprietary technology and rendered on Genpio'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://genpio.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 Genpio's
own multilingual cloned voices, trained by Genpio and running on Genpio's GPUs.
There is no third-party TTS anywhere in the catalogue.

**Architecture and rights.** Genpio 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.
Genpio 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; Genpio holds a permanent
Apache-2.0 grant over them. The weights carry Genpio's own licence rather than
Apache-2.0.

The model is published as a model card at
<https://huggingface.co/GuidenAI/genpio_voice>: 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, 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 trained
**85+ languages**, which is also what the product sells in, while FLEURS 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": "genpio: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 `genpio:` |
| `Gender` | string | `Female` or `Male` |
| `Locale` | string | Language code, e.g. `vi`, `zh`, `yue` |
| `LocaleName` | string | Human-readable language name |
| `EditorsChoice` | boolean | On the hand-picked shortlist (see below) |

**Query parameters**

| Name | Notes |
| --- | --- |
| `picks` | `1`, `true` or `yes` returns only the editor's choice voices |

Editor's choice is a hand-picked shortlist of around 26 voices — a male and a
female for each of the main markets — for callers that want a representative
sample rather than the whole catalogue. It is what the homepage voice demo
loads. 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 Genpio's own models, so there is no
provider or brand field to filter on — the whole list is Genpio Voice.

`Locale` is the voice's primary language, not a limit: every Genpio 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 Genpio admin panel appear here within the
server's refresh window.

---

## GET /api/jobs

Open roles at Genpio, the same list rendered at https://genpio.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 Genpio backend and its upstream dependencies. This is the
data behind https://genpio.com/status.

**Response** `200 application/json`, `Cache-Control: no-store`

```json
{
  "ok": true,
  "service": "Genpio AI Backend",
  "uptimeSeconds": 21195,
  "timestamp": "2026-07-17T07:00:16.767Z",
  "liveSessions": 0,
  "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 `Genpio AI Backend` |
| `uptimeSeconds` | number | Seconds since this server process started |
| `timestamp` | string | ISO 8601 timestamp of the probe |
| `liveSessions` | number \| null | Active sessions reported by the media upstream, or `null` if unavailable |
| `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 genpio.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://genpio.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 `genpio-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": "Genpio 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://genpio.com/mcp/server-card), mirrored at
[`/.well-known/mcp/server-card.json`](https://genpio.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://genpio.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`,
`genpio_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://genpio.com/api/jobs/{id}/jd` for a
single job description.

**Prompts**: `choose_a_voice` (arguments `language`, optional `audience`) and
`genpio_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": "Genpio 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 genpio.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
(`genpio_find_voice`, `genpio_list_avatars`, `genpio_list_open_roles`,
`genpio_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: `genpio_navigate` moves the tab between pages on this
site, and `genpio_read_page` returns the current route as Markdown.

---

**Not this server**: the Genpio product app has its own MCP server at
https://meet.genpio.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://genpio.com/llms-full.txt) under "Connect an AI
Assistant", and in [`/auth.md`](https://genpio.com/auth.md).

---

## See also

- MCP server card: https://genpio.com/mcp/server-card
- Product app MCP server (accounts, billing, scripts, products): https://meet.genpio.com/mcp
- AI catalog (every agent artifact on this origin): https://genpio.com/.well-known/ai-catalog.json
- A2A agent card: https://genpio.com/.well-known/agent-card.json
- Site overview for LLMs: https://genpio.com/llms.txt
- Full product reference: https://genpio.com/llms-full.txt
- Machine-readable API catalogue: https://genpio.com/.well-known/api-catalog
- Sitemap: https://genpio.com/sitemap.xml
