API reference
A REST API over HTTPS with JSON in and JSON — or audio — out. Every route below is mounted and auth-gated.
Base URL
Every route on this page lives under https://api.arapulse.com/api/v1. Requests are JSON over HTTPS; responses are JSON, or the audio file itself on the synthesis endpoints. The one exception to the prefix is GET /health, the bare liveness probe.
Authentication
Two credential types. A sk_live_ or sk_test_ secret key authenticates server-to-server calls and must never reach a browser. For client-side use, mint a short-lived st_ session token from your own backend and hand that over instead.
# Server to server — a secret key.
curl https://api.arapulse.com/api/v1/characters \
-H "Authorization: Bearer sk_live_..."
# Browser — a short-lived session token you minted server-side.
curl https://api.arapulse.com/api/v1/avatars/abc/chat \
-H "Authorization: Bearer st_..."Key prefixes are sk_live_ and sk_test_. Earlier documentation referred to a ck_live_ prefix. That format was never issued — if you are matching on it, that is why nothing validates.
Errors and limits
Errors come back with a consistent envelope and a meaningful status code. Quota exhaustion is a 429 and is refused rather than silently billed.
{
"success": false,
"error": {
"message": "Daily quota exceeded for this API key",
"statusCode": 429,
"timestamp": "2026-08-01T19:14:08.508Z"
}
}- 401 — missing or invalid credential.
- 403 — the key is valid but lacks the scope.
- 429 — rate limit or daily quota exhausted.
- 501 — the capability exists but is not connected yet, such as adding a payment method.
- 503 — an upstream model container is unavailable. Check status.
Rate limiting is applied per key across the whole /api/v1 surface, and daily quotas are enforced on every key-authenticated route rather than only at the edge.
Live sessions
Create a companion session, exchange turns, and issue viewer tokens. Sessions are published into a WebRTC room.
| Method | Path | Description |
|---|---|---|
| POST | /api/v1/avatars | Create a real-time avatar session. |
| POST | /api/v1/avatars/:id/chat | Send a turn and receive her reply. |
| POST | /api/v1/avatars/:id/viewer-token | Issue an extra viewer token for the room. |
| GET | /api/v1/avatars/:id | Fetch session state. |
| DELETE | /api/v1/avatars/:id | End the session and release capacity. |
Speech
Synthesis and transcription. Both run on self-hosted models.
| Method | Path | Description |
|---|---|---|
| POST | /api/v1/text-to-speech | Synthesise a complete WAV. |
| POST | /api/v1/text-to-speech/stream | Synthesise in chunks for early playback. |
| POST | /api/v1/text-to-speech/with-timestamps | Audio plus per-word offsets (interpolated). |
| POST | /api/v1/speech-to-text | Transcribe multipart or raw audio. |
| POST | /api/v1/speech-to-text/stream | Proxies upstream; returns one response, not a live feed. |
| GET | /api/v1/speech-to-text/health | Upstream transcription health. |
Voices
Manage the voice library. Submit a reference clip and the clone is fetched, transcribed and registered; poll the voice until it reports active or failed.
| Method | Path | Description |
|---|---|---|
| GET | /api/v1/voices | List available and cloned voices. |
| POST | /api/v1/voices | Create a voice. With samples, cloning runs and the voice activates. |
| GET | /api/v1/voices/:id | Fetch one voice. |
| DELETE | /api/v1/voices/:id | Delete a voice. |
Characters
A character carries appearance, personality and its bound voice together.
| Method | Path | Description |
|---|---|---|
| GET | /api/v1/characters | List characters. |
| POST | /api/v1/characters | Create a character. |
| GET | /api/v1/characters/:id | Fetch one character. |
| PATCH | /api/v1/characters/:id | Update appearance or personality. |
| DELETE | /api/v1/characters/:id | Delete a character. |
Keys and session tokens
Mint a short-lived token server-side and hand that to the browser. Your secret key never reaches a client.
| Method | Path | Description |
|---|---|---|
| POST | /api/v1/sessions/token | Mint a short-lived browser token (st_). |
| GET | /api/v1/sessions | List active session tokens. |
| DELETE | /api/v1/sessions/:id | Revoke a session token. |
| GET | /api/v1/developer-sessions | Developer-side session management. |
| GET | /api/v1/admin/keys | Manage API keys. |
Account, usage and teams
Profile, analytics, plans and organisation membership.
| Method | Path | Description |
|---|---|---|
| GET | /api/v1/user | Profile and settings. |
| GET | /api/v1/analytics | Usage by key and endpoint. |
| GET | /api/v1/billing/plans | Available plans. Public. |
| GET | /api/v1/billing/current | Current subscription. |
| GET | /api/v1/organizations | Organisations, roles and shared quota. |
| GET | /api/v1/webhooks | Manage endpoints and inspect recent deliveries. |
| POST | /api/v1/webhooks/:id/test | Fire a test delivery at your endpoint. |
| DELETE | /api/v1/user/account | Erase the account. `?dryRun=true` reports without deleting. |
Public
The only endpoints that need no credentials at all.
| Method | Path | Description |
|---|---|---|
| GET | /api/v1/status | Service health. Rate limited, cached 15s. |
| GET | /api/v1/demo/voices | Voices the homepage demo may use. |
| POST | /api/v1/demo/tts | Homepage demo synthesis. Tight per-IP limit. |
| GET | /health | Platform liveness. |
Webhooks
Register an endpoint and subscribe it to any of the seven events. Deliveries retry with backoff on failure, and recent attempts are visible on the webhook record.
| Event | Fires when |
|---|---|
| session.started | A live companion session opens. |
| session.ended | A session closes — explicitly, by idle reap, or by eviction. |
| character.created | A character is created. |
| character.updated | A character changes. Carries changed field names, not values. |
| character.deleted | A character is deleted. |
| tts.completed | Speech synthesis finishes. |
| voice.created | A voice becomes usable, including after a clone completes. |
Verifying a delivery
Each request carries an X-AraPulse-Signature-V2 header of the form t=<timestamp>,v1=<signature>. The signature is an HMAC-SHA256 over `${timestamp}.${rawBody}` using your webhook secret.
import crypto from "crypto";
export function verify(rawBody, header, secret) {
const parts = Object.fromEntries(
header.split(",").map(p => p.split("="))
);
// Reject replays before spending time on the digest.
const age = Math.abs(Date.now() / 1000 - Number(parts.t));
if (!(age < 300)) return false;
const expected = crypto
.createHmac("sha256", secret)
.update(`${parts.t}.${rawBody}`)
.digest("hex");
// Constant time — a plain === leaks the digest byte by byte.
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(parts.v1)
);
}Compare in constant time and check the timestamp. The timestamp is inside the signed payload specifically so a captured request cannot be replayed later. Reject anything older than five minutes, and use a timing-safe comparison — a plain === on the digest leaks it byte by byte.
Spec coverage
The OpenAPI document currently describes a subset of the mounted routes, so this page is ahead of the machine-readable spec. If you are generating a client from the spec, expect gaps in analytics, billing, organisations, webhooks and speech-to-text, and refer to this page for those.