MASHBOXLabs
Request access

Docs

The API serves MASHBOX Creature and Maya1 text to speech to approved companies. Once approved, you create a key and call either one over HTTP.

Quickstart

  1. Request access: fill in the business access form. Access is free for approved businesses.
  2. Once approved, create a key on the keys page. It's shown once, so copy it.
  3. Send a request with the key in the Authorization header, and read the result: the audio file, or a job you poll until it's done.
cURL
export MASHBOX_API_KEY="your key"
curl https://inference.mashbox.gg/v1/models -H "Authorization: Bearer $MASHBOX_API_KEY"

Calling the API

  • HTTP with JSON bodies, from any language. Base URL: https://inference.mashbox.gg/v1
  • OpenAI SDK. Text to speech takes the OpenAI speech request. Set the base URL to https://inference.mashbox.gg/v1 and use your MASHBOX key. See OpenAI compatibility.

Authentication

Send Authorization: Bearer $MASHBOX_API_KEY on every request. Create and manage keys on the keys page; a new key is shown once. Keys belong on servers: a request that carries a browser Origin header is refused with origin_not_allowed.

GET /v1/key describes the key you call it with.

MASHBOX Creature

Pipeline Experimental For approved accounts

Experimental: output quality is still improving; results may need cleanup.

One text prompt in; a textured, rigged and animated 3D creature out, as a GLB file. POST /v1/creatures with model mbx-creature answers 202 at once with a job: its id and status. The job also gives position, its place in the queue, eta_s, the estimated seconds until it is done, progress, how far along it is and stage, the step it is on. Poll the job until it is completed, then download the files. A creature takes minutes, not seconds. You can also try it in the playground.

Each request runs these steps in order:

  1. A concept image from your prompt, made with Z-Image Turbo.
  2. A 3D mesh built from that image.
  3. Texture painted onto the mesh.
  4. A skeleton and skin weights fitted to the mesh.
  5. Motion clips for that skeleton, from our own motion model.
  6. Everything packed into one GLB file for your game.

Request fields

FieldTypeRequiredAbout
bodystringnoBody type. auto picks for you; humanoid builds a two-legged figure. metadata.json says which rig route ran. One of auto, creature, humanoid. Default auto.
modelstringnoThe model id, mbx-creature.
motionsarraynoOne animation clip per entry, described in plain words. Default: walk, trot, walk and turn left, leap, pounce, die.
promptstringyesWhat to make. Up to 200 characters.
seedintegernoSame seed + same input = same output. From 0, to 2,147,483,647. Default 0.
stylestringnoArt style. Leave it out for the default realistic look. One of anime, cartoon, storybook, horror.
cURL
curl https://inference.mashbox.gg/v1/creatures \
  -H "Authorization: Bearer $MASHBOX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model": "mbx-creature", "prompt": "a six-legged desert lizard with a spiked tail"}'

# then, every 2 seconds until status is completed or failed:
curl https://inference.mashbox.gg/v1/jobs/$JOB_ID -H "Authorization: Bearer $MASHBOX_API_KEY"
Python
import os, time, requests

H = {"Authorization": "Bearer " + os.environ["MASHBOX_API_KEY"]}
job = requests.post("https://inference.mashbox.gg/v1/creatures", headers=H, json={"model": "mbx-creature", "prompt": "a six-legged desert lizard with a spiked tail"}, timeout=60).json()
while job["status"] not in ("completed", "failed", "cancelled"):
    time.sleep(2)
    job = requests.get("https://inference.mashbox.gg/v1/jobs/" + job["id"], headers=H, timeout=60).json()
print(job)

Maya1 text to speech

Model For approved accounts

Text in, speech out, in a voice you describe in plain words. POST /v1/audio/speech with model maya1 answers with the audio itself. Built on Maya1 by Maya Research.

Request fields

FieldTypeRequiredAbout
inputstringyesText to speak, 1 to 4,096 characters. Put any of the tags below anywhere in the text; the server never adds, removes or moves a tag.
instructionsstringnoTone, emotion or style, added to the end of the voice description.
modelstringyesmaya1
response_formatstringyesSend wav or pcm on every request: 24 kHz, mono, 16-bit. Other formats are not served for this model yet.
stream_formatstringnoaudio = the audio bytes, streamed as they are made; sse = server-sent events speech.audio.delta {audio: base64} then speech.audio.done {usage}. One of audio, sse. Default audio.
voicestring or objectyesmale or female (neutral presets), {"id": "male"} or {"id": "female"}, or {"description": "..."}: a voice you describe in plain words, 1 to 1,000 characters. The same description always gives the same voice.
cURL
curl https://inference.mashbox.gg/v1/audio/speech \
  -H "Authorization: Bearer $MASHBOX_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model": "maya1", "input": "Hold the gate! They'\''re coming over the ridge.", "voice": {"description": "A gruff old blacksmith with a deep, slow voice."}, "response_format": "wav"}' \
  --output speech.wav

One request makes up to 120 seconds of audio. The male and female presets are neutral: emotion comes from the tags below and from emotion and intensity words in a voice description, for example an excited young woman, high intensity.

Emotion tags

Put a tag where the sound or change of delivery should happen. Results depend on the text, the voice description and where the tag sits, so try a few placements. Only these 20 tags work.

TagExample input
<angry>You took the last boat without asking. <angry> That was ours.
<appalled><appalled> You left the generator running all night with the door shut?
<chuckle>Well, that went about as well as I expected. <chuckle> Next time, read the map.
<cry>I waited at the dock until it got dark. <cry> Nobody came.
<curious><curious> What's that light out past the breakwater? It wasn't there yesterday.
<disappointed>We drove four hours for this. <disappointed> The museum's closed on Mondays.
<excited><excited> They found the radio! It still works, listen to this!
<exhale>Okay. <exhale> We're through the worst of it now.
<gasp>Turn around slowly. <gasp> Don't move, it's right behind you.
<giggle>You put salt in the coffee again. <giggle> I'm not drinking that.
<gulp>The bridge is out, so we go across the ice. <gulp> Right. Sure.
<laugh>He tried to fix the roof with duct tape. <laugh> In November.
<laugh_harder>And then the goat ate his hat. <laugh_harder> The whole hat!
<mischievous><mischievous> I might have borrowed the key to the lighthouse.
<sarcastic><sarcastic> Oh great, another storm warning. What a surprise.
<scream>Get away from the edge! <scream> Now!
<sigh>I fixed the fence again this morning. <sigh> The deer will be back by Friday.
<sing><sing> Row, row, row your boat, gently down the stream.
<snort><snort> You call that a fish? I've seen bigger bait.
<whisper><whisper> Keep your voice down. They're sleeping in the next room.

Real-time voice conversation, speech in and speech out: coming soon.

Jobs

Creatures run as jobs. The request answers at once with the job's id and status. Poll GET /v1/jobs/{id} about every 2 seconds. Statuses: queued, in_progress, moderating, completed, failed, cancelled. Cancel with POST /v1/jobs/{id}/cancel.

  • Idempotency-Key: send the same key to retry a request safely.
  • Outputs: signed URLs that last 10 minutes. Fresh ones come from GET /v1/jobs/{id}/files. Outputs are kept 7 days.

Errors

Errors use the OpenAI error shape, with extra fields that say what to do next: request_id, limit_source, remedy, resets_at and docs, a link to the row below. Busy answers are 429 with Retry-After; wait that many seconds and retry. A model_loading answer also carries error.loading with state and eta_s. insufficient_quota is a 429 without Retry-After: retrying does not help.

HTTPCodeMeaningWhat to do
400invalid_jsonThe body is not valid JSON for this route, or the Content-Type is not application/json.Send a JSON body with Content-Type: application/json.
400invalid_requestThe request is malformed for this route.Check the route's request schema.
400invalid_parameterA parameter is missing, of the wrong type or out of range; param names it.Fix the named parameter.
400unsupported_parameterThis model does not take one of the parameters you sent; param names it.Remove it. GET /v1/models lists each model's supported_parameters.
400unknown_parameterThe request includes an argument the API does not recognize; the message names it. Nothing was run.Remove the unrecognized argument.
400invalid_valueA parameter has a value outside the set this model serves (OpenAI's invalid_value wording); param names it and the message lists the supported values.Send one of the listed values.
400unsupported_shapeThe size, duration or shape is not one this model serves.Use one of the values listed in the message or the model's input_schema.
400precision_unavailableThe asked precision tier is not offered for this model.Ask for a listed precision (GET /v1/models precisions) or none for the default.
400prompt_too_longThe prompt is over the model's length limit.Shorten the prompt.
400invalid_input_fileThe uploaded input file is missing, unfinished or not valid for this model.Upload again (POST /v1/uploads, then PUT the exact bytes) and pass its id.
401invalid_api_keyThe API key or session is missing or not valid.Send Authorization: Bearer <key>.
401api_key_expiredThe API key has expired.Create a new key.
401session_expiredThe browser session has expired.Sign in again.
401recent_sign_in_requiredCreating a key needs a sign-in within the last 15 minutes.Sign in again, then retry.
403account_pausedThe account is paused.Contact support with the request id.
403human_check_failedThe human check (Turnstile) is missing or failed.Complete the check and retry once.
403key_disabledThis key is disabled.Enable it or use another key.
403model_not_allowedThis key is not allowed to use this model.Add the model to the key's allowed_models.
403not_permittedThis action is not permitted for this account or key.See the message.
403origin_not_allowedAPI keys are not accepted from browsers (an Origin header was sent).Call the API from your server; the web app uses sessions.
404route_not_foundNo such route.Check the path against these docs.
429evaluation_quota_reachedThe free evaluation quota for this model is used up for the rolling window (resets_at says when it frees up).Talk to us for more.
429demo_daily_capacity_reachedToday's free evaluation capacity for this model is used up. It comes back at 00:00 UTC.Retry after resets_at, or talk to us for dedicated capacity.
404model_not_foundNo model with this id (or it does not serve this endpoint).List models with GET /v1/models.
404job_not_foundNo job with this id for this account.Check the id.
404not_foundThe object does not exist.Check the id.
405method_not_allowedThis route does not accept that HTTP method.Check the method against these docs.
409idempotency_key_in_flightA request with this Idempotency-Key is still being admitted.Retry after Retry-After; the first request's job is returned then.
409job_not_cancellableThe job already finished.Nothing to do.
409not_readyThe video is not finished yet.Poll until status is completed.
413request_too_largeThe request body is over this route's size limit.Send a smaller body; upload files with POST /v1/uploads.
413file_too_largeThe file is over the size limit.Send a smaller file.
403bad_signatureThe signed file URL is wrong or has expired.Ask for a fresh URL (GET /v1/jobs/{id}/files or a new upload).
403evaluation_access_requiredThis model is open to approved evaluation accounts only.Request free evaluation access at https://mashbox.gg/access.
409upload_not_pendingThis upload URL was already used.Create a new upload.
422idempotency_key_reusedThis Idempotency-Key was used with a different body (keys are kept 24 h).Use a new key for a new request.
410output_expiredThe output is past its retention period.Run the job again.
415unsupported_media_typeThis file type is not accepted here.Use one of the types named in the message.
429rate_limit_exceededToo many requests per minute for the key, account or network (limit_source says which).Wait Retry-After seconds.
429concurrency_limit_exceededToo many requests in flight at once.Wait for one to finish.
429demo_budget_exhaustedToday's demo budget is used up.Sign in for the free tier.
429capacity_busyEvery slot serving this model is busy. error.capacity gives slots_busy, queued_ahead and eta_s, the seconds until a slot is expected to be free; eta_basis says how that time was worked out.Retry after Retry-After seconds.
429capacity_unavailableThis tier is not available right now.Use the tier named in the message, or retry after Retry-After.
429model_unavailableThe model is paused or not served right now.Retry after Retry-After seconds.
429queue_fullEvery slot serving this model is busy and its queue is full. error.capacity has the same fields as for capacity_busy.Retry after Retry-After seconds.
429model_loadingThe model is loading. error.loading gives its state and eta_s, the seconds until it is ready; eta_basis says how that time was worked out.Retry after Retry-After seconds.

Limits

Each account and each key has a request rate and a number of runs in flight. Every response carries X-RateLimit-Limit-Requests, X-RateLimit-Remaining-Requests and X-RateLimit-Reset-Requests. GET /v1/limits returns your current limits as JSON. Over a limit you get 429 with Retry-After.

Evaluation quota

Each approved company gets 10 creatures and 30 minutes of audio per 30 days, free. Need more? Write to [email protected]. When it is used up, requests answer 429.

When a demo is paused

A paused demo answers 429 model_unavailable with Retry-After. GET /v1/status shows which demos are live.

OpenAI compatibility

Text to speech works with the official OpenAI SDKs: point the client at https://inference.mashbox.gg/v1 and pass your MASHBOX key. Errors use the OpenAI error shape. Creatures have no OpenAI equivalent; call them over HTTP.

Python
from openai import OpenAI
import os

client = OpenAI(base_url="https://inference.mashbox.gg/v1", api_key=os.environ["MASHBOX_API_KEY"])
with client.audio.speech.with_streaming_response.create(
    model="maya1",
    input="Hold the gate! They're coming over the ridge.",
    voice={"description": "A gruff old blacksmith with a deep, slow voice."},
    response_format="wav",
) as r:
    r.stream_to_file("speech.wav")

Models

GET /v1/models lists what the API serves right now. It's public: no key needed.

cURL
curl https://inference.mashbox.gg/v1/models

Status

GET /v1/status gives each model's live state. Public, no key needed.

cURL
curl https://inference.mashbox.gg/v1/status

Packs

The other 11 catalog models are available as Packs, with example outputs for each on the Packs page. Write to [email protected].

Measured results are on the research page.