Masky Developer API
Drive Masky — the AI avatar platform — over HTTP. Generate images, create avatars (image + personality + voice), run conversations that render spoken audio and talking-head video, embed an auto-playing live viewer, and add “Login with Masky” SSO to your own site.
curl.Overview
Base URL: https://masky.ai/api. All request and response bodies are JSON (Content-Type: application/json). Avatars each have a still image, a personality prompt, and a voice; you start a conversation with one and inject turns, each of which renders audio and optional video. Conversations and individual turns expose embeddable live-viewer URLs.
Authentication
Get a key at masky.ai/developer (sign in → Generate key → copy the mky_… token, shown once). Send it on every request:
Authorization: Bearer mky_...
Access tokens minted through Login with Masky are also mky_ keys and work on these same endpoints, limited to their granted scopes. The avatar owner is billed for generation — when a token renders someone else's publicly-renderable avatar, the owner pays, not the caller.
Conventions & credits
- Async turns. A conversation turn returns immediately with a
pendingshell; audio/video URLs land on the same turn doc as the worker finishes (poll, or just embed the live URL). Text ≈ 3s, audio ≈ 5–10s, video ≈ 30–60s. - Auto-chunking. Long replies split into ~3-sentence pieces; each becomes its own turn doc and the live URL plays them seamlessly in order.
- Failures are free. Image and render charges apply only on success.
- 1 credit = $1. Spoken turns (audio / talking-head video) are billed per second of speech, estimated from the text — so a cost can be quoted before rendering.
| Action | Credits |
|---|---|
Image generate (/images/generate) | 0.005 |
Image edit (/images/edit) | 0.01 |
| Spoken audio (per second) | ~0.0015 / sec |
| Talking-head video (per second of speech) | ~0.0265 / sec |
| Video generation (per second of output) | ~0.02–0.03 / sec |
POST/images/generate
Text-to-image. Synchronous — returns when rendered & stored. Requires the generate scope for SSO tokens.
curl -s -X POST https://masky.ai/api/images/generate \
-H "Authorization: Bearer $MASKY_API_KEY" -H "Content-Type: application/json" \
-d '{"prompt":"photorealistic portrait of a friendly robot barista","aspectRatio":"1:1"}'
# 200 -> { "imageUrl": "https://.../img.jpeg", "aspectRatio": "1:1", "creditCost": 0.005 }
| Field | |
|---|---|
prompt | required, ≤ 2000 chars. |
aspectRatio | 1:1 (default), 16:9, 9:16, 4:3, 3:4, 3:2, 2:3. |
The returned imageUrl is a long-lived public URL — pass it straight into create avatar.
POST/images/edit
Edit/compose from 1–5 reference images. Requires the generate scope for SSO tokens.
curl -s -X POST https://masky.ai/api/images/edit \
-H "Authorization: Bearer $MASKY_API_KEY" -H "Content-Type: application/json" \
-d '{"prompt":"put her in a spacesuit","imageUrl":"https://.../portrait.jpeg"}'
# 200 -> { "imageUrl": "https://.../edited.jpeg", "aspectRatio": "match_input_image", "creditCost": 0.01 }
prompt | required, the edit instruction. |
imageUrl / images[] | one URL, or an array of up to 5 http(s) URLs (required). |
aspectRatio | defaults to match_input_image; or any value from generate. |
POST/videos/generate
Render a short video via Masky's video models — text-to-video, image-to-video, or video editing. Async (renders take ~1–3 min): this returns 202 with a generationId; poll GET /videos/{id} until status is video. Requires the generate scope for SSO tokens. Charges 1 credit on success.
Avatar-aware. Reference your avatars by name in the prompt (or pass avatarIds) and their images are sent to an image-conditioned model so they actually appear in the video.
curl -s -X POST https://masky.ai/api/videos/generate \
-H "Authorization: Bearer $MASKY_API_KEY" -H "Content-Type: application/json" \
-d '{"prompt":"Aphrodite walks along a moonlit beach, camera slowly pushing in","resolution":"720p"}'
# 202 -> { "generationId":"…", "status":"pending", "model":"cinematic",
# "referencedAvatars":[{"id":"…","name":"Aphrodite"}], "poll":"/api/videos/…" }
prompt | required. Mention avatars by name to feature them. |
avatarIds | optional explicit avatars to feature (up to 3), added to any named in the prompt. |
image | optional explicit conditioning image URL (image-to-video). |
srcVideo | optional source video URL to edit (uses ensemble) — combine with a prompt describing the change. |
model | override the auto-pick: cinematic (default; text→video + single-image→video), motion (image→video), scene (text→video), ensemble (1–3 reference images for character consistency, or video editing). |
resolution, aspectRatio, duration, seed | passed through (model-specific; e.g. cinematic resolution is 720p or 1080p). |
Model auto-pick: 0–1 reference image → cinematic; 2–3 → ensemble (character consistency); a srcVideo → ensemble (video-to-video edit).
GET/videos/{generationId}
Poll a video job. { status: "pending" | "video" | "error", videoUrl, model }. videoUrl is a long-lived public URL once status is video.
curl -s https://masky.ai/api/videos/$ID -H "Authorization: Bearer $MASKY_API_KEY"
# -> { "status":"pending" } …then… { "status":"video", "videoUrl":"https://…/vid.mp4" }
GET/avatars
List the avatars you own. GET /avatars/public lists avatars others have marked publicly renderable.
POST/avatars
Create an avatar. The image URL is fetched and stored by Masky — supply a public URL (e.g. the imageUrl from /images/generate).
curl -s -X POST https://masky.ai/api/avatars \
-H "Authorization: Bearer $MASKY_API_KEY" -H "Content-Type: application/json" \
-d '{"displayName":"Dragon","imageUrl":"https://.../dragon.jpeg",
"personalityPrompt":"A wise, theatrical dragon.","voiceId":""}'
# 200 -> { "avatarId": "...", "avatarOwnerUserId": "..." }
displayName | required. |
imageUrl | public image URL (fetched + stored). |
personalityPrompt | character spec used in chat mode. |
voiceId | from /voices. |
knowledgeBaseUrls, publiclyRenderable | optional. |
GET/voices
Preset voices plus any custom voices you already have. Call before create/set-voice. Attach one with POST/avatars/{avatarId}/voice { voiceId }. Cloning a custom voice from audio samples is done in the web UI; cloned voices then appear here.
POST/avatars/{avatarId}/public
{ publiclyRenderable: true|false } — toggle whether other developers can render this avatar (you pay the credits when they do).
POST/avatars/{avatarId}/speak
One-shot generation — a single TTS or talking-head clip with no conversation thread. Async: returns a generation you poll with GET/avatars/speak/{generationId} (status pending → audio → video or error; includes audioUrl, videoUrl, shareSlug).
{ "text": "Hello!", "textMode": "literal", "output": "video" }
text | required, ≤ 500 chars. |
textMode | literal (default) or personality (rewrites via the avatar's prompt). |
output | audio (default) or video. |
Identical requests are deduplicated (HTTP 200 reused:true, no double charge). Public play link: GET/g-YY-MM-XXXX 302s to a signed URL.
POST/conversations
Start a conversation with an avatar.
curl -s -X POST https://masky.ai/api/conversations \
-H "Authorization: Bearer $MASKY_API_KEY" -H "Content-Type: application/json" \
-d '{"avatarId":"...","avatarOwnerUserId":"..."}'
# 200 -> { conversationId, shareSlug, viewerToken, liveUrl }
liveUrl (https://masky.ai/live/c-…?token=…) is the embeddable viewer — avatar image fills the window, audio/video overlays on top, and each new turn auto-plays. Drop it in an OBS Browser Source or any web page.
POST/conversations/{conversationId}/turn
Inject a turn; the avatar replies asynchronously. Requires the generate scope for SSO tokens.
curl -s -X POST https://masky.ai/api/conversations/$ID/turn \
-H "Authorization: Bearer $MASKY_API_KEY" -H "Content-Type: application/json" \
-d '{"userText":"Tell me about Masky.","output":"video","mode":"chat"}'
userText | required — what is said to the avatar. |
output | text | audio | video (default audio). No-voice avatars soft-degrade to text. |
mode | chat (default; Gemini replies using the avatar's personality + history) or speak (says userText verbatim). |
reinterpret | in speak mode, rewrite userText through the avatar's personality before TTS. |
The conversation accepts the internal id or the c-… share slug in the path. Conversation is the caller's (or its owner's) — turns spend the owner's credits.
Two-avatar turns
Render both sides of an exchange in one call — a second avatar of yours speaks the user line, the conversation avatar speaks a scripted reply:
{
"userText": "So what is this, in one breath?",
"avatarText": "An API that turns text into avatars who talk.",
"speakerAvatarId": "<your second avatarId>",
"userOutput": "video",
"output": "video",
"mode": "chat"
}
speakerAvatarId | render the user turn as a second avatar you own (its face + voice). |
userOutput | text | audio | video for that speaker turn. |
avatarText | exact line the conversation avatar speaks (skips Gemini). |
GET/conversations/by-slug/{slug}
Returns the conversation and all turns with freshly re-signed media. Poll your turn by id — done when status=="ready" (text), audioUrl set (audio), or videoUrl set (video); status=="error" + errorMessage on failure. Once a turn has media it also carries:
shareSlug | t-YY-MM-XXXX. |
shareUrl | https://masky.ai/t-… — a static social-card page that plays the clip. |
liveUrl | https://masky.ai/live/t-… — the live viewer scoped to that one turn (play/pause, sound + volume, caption translation). No token needed; the slug is the grant. |
POST/conversations/{conversationId}/visibility
{ isPublic: true|false }. Public conversations are readable by slug without auth; private ones require the owner.
Live viewer URLs
- Whole conversation — the conversation's
liveUrl:https://masky.ai/live/c-YY-MM-XXXX?token=…. Plays every turn in order; append?since=now(or#new) to play only turns created after load. - Single turn — a turn's
liveUrl:https://masky.ai/live/t-YY-MM-XXXX. Same UI, just that one turn.
Both pages support play/pause, sound + a volume slider, and live caption translation into any language. The ?token= on a conversation live URL is the access grant — treat it like a secret for private conversations.
Share links
GET /g-YY-MM-XXXX (one-shot speak) and GET /t-YY-MM-XXXX (conversation turn) are public, no-auth links that 302 to a freshly-signed media URL and render a social card. Year/month are UTC; the code is four lowercase letters/digits.
Login with Masky (SSO)
Masky is an OAuth 2.0 authorization-code identity provider. The twist: the user picks which of their avatars represents them on your site — the avatar's name + photo become the identity you receive, and with the generate scope you can call this API as that avatar, on their credits.
| Scope | |
|---|---|
profile | identity (name, picture, pseudonymous sub) via /userinfo. |
avatars:read | list the user's avatars. |
generate | spend the user's credits on images / speak / turns. |
POST/oauth/clients first-party key
Register your app (also available as the MCP tool register_oauth_client). Authenticate with your own mky_ key.
curl -s -X POST https://masky.ai/api/oauth/clients \
-H "Authorization: Bearer $MASKY_API_KEY" -H "Content-Type: application/json" \
-d '{"name":"My App","redirectDomains":["myapp.com"],"scopes":["profile","generate"]}'
# -> { "clientId":"mkc_…", "clientSecret":"mks_…", ... } # secret shown ONCE
GET /oauth/clients lists your apps; DELETE /oauth/clients/{clientId} removes one.
Authorize (consent)
Send the user to the consent screen. redirect_uri's host must be in your redirectDomains (https only, except localhost). PKCE (code_challenge/S256) is supported for public clients.
https://masky.ai/oauth-authorize.html?client_id=mkc_…&redirect_uri=https://myapp.com/callback&scope=profile%20generate&state=<csrf>
They approve and Masky redirects to redirect_uri?code=…&state=….
POST/oauth/token
curl -s -X POST https://masky.ai/api/oauth/token \
-H "Content-Type: application/json" \
-d '{"grant_type":"authorization_code","code":"<code>",
"redirect_uri":"https://myapp.com/callback",
"client_id":"mkc_…","client_secret":"mks_…"}'
# -> { "access_token":"mky_…", "token_type":"Bearer",
# "scope":"profile generate", "avatar":{ "id","name","picture" } }
The access_token is a scoped mky_ key — use it as Authorization: Bearer on any endpoint here (subject to its scopes). Auth codes are single-use and expire in 5 minutes.
GET/oauth/userinfo
curl -s https://masky.ai/api/oauth/userinfo -H "Authorization: Bearer <access_token>"
# -> { "sub":"ava_…", "name":"Aphrodite", "picture":"https://…",
# "avatar_id":"…", "scope":"profile generate" }
sub is a stable pseudonym per (user, your app, avatar) — never the raw Masky uid, and un-correlatable across sites. Treat it as the user's account id on your side.
Connected apps
A user can review and revoke authorized apps at masky.ai/developer. Programmatically: GET/oauth/grants lists a user's granted tokens (first-party key) and DELETE/oauth/grants/{id} revokes one.
OpenAPI
Machine-readable specs: masky-openapi.json (full API) and openapi.json (avatar-speak). Load either into Postman, Swagger UI, or a codegen tool.