Media
Generate images, video, audio and speech from a catalog of hosted models, transcribe recordings, and ask questions about media you supply — outputs arrive as signed artifact URLs.
Media is a CoreSpeed-hosted capability: no third-party account to connect, and
the same /mcp seam as everything else, so identity, organization holds,
API-key caps, activity recording, and capability visibility all apply. It does
three things: generation from a catalog of more than a thousand image, video,
audio and speech models; understanding, which answers a question about a
video, image, or audio file you point it at; and transcription, which turns
speech in a recording into text.
Tools
| Tool | Use it for |
|---|---|
media__list_models | Search the generation catalog with credit rates. No arguments returns the featured picks. |
media__get_model | One model's input schema, vendor, and current rate. Call it before generating. |
media__generate | Run a model. Waits inline up to wait_seconds, otherwise hands back a running job. |
media__get_result | A job's outcome: fresh signed URLs for a finished job, or its current status. |
media__list_jobs | This organization's recent jobs, running and finished. Receipts are kept about 30 days. |
media__cancel | Stop a running job. Canceled jobs are never charged. |
media__understand | Ask a question about a video, image, or audio file and get a text answer. |
media__transcribe | Turn speech in an audio or video file into text, with speaker labels and optional word timings. |
media__list_voices | Find a voice in Fish Audio's public library for the speech models. |
Generate: pick a model, then run it
media__list_models— search by name,kind(image, video, or audio), or exactcategory, and choose a model id.media__get_model— read itsinput_schemaand structuredprice.media__generate— buildinputagainst that schema. Files come back inline, or as ajob_idthat finishes server-side.
The schema and the rate both come from the catalog, never from the client.
input is validated against the model's own input_schema. Unknown top-level
keys are rejected — the schema's properties are the allowlist — and safety
parameters are policy, not input: whatever the caller sends for a safety*,
nsfw*, or moderation* key is discarded and the safe value is enforced on the
server. When the upstream model rejects an input anyway, its own validation
detail is returned to you, unbilled.
File-typed inputs accept two sources:
- a public
httpsURL fetchable without authentication; - a file in your Drive — its path or file id: an upload
(
drive__create_upload, below), a connector download, a mail attachment, or an earlier generation's output — so image → video chains stay entirely inside CoreSpeed.
A model needs a rate before it can run
price in the catalog is structured, and it is a rate, not a total:
{ "state": "priced", "credits_per_unit": 4, "unit": "image" }{ "state": "unavailable" } carries no rate fields and means there is no rate
right now. Generation is refused (pricing_unavailable, retryable) rather than
billed blind — retry shortly.
Long generations become jobs
wait_seconds (default 45, max 300) is the time budget for the whole call. The
server answers inside it, one of two ways:
{
"model": "fal-ai/flux/schnell",
"kind": "image",
"job_id": "job_…",
"status": "succeeded",
"assets": [
{
"id": "art_…",
"mimeType": "image/png",
"size": 1284021,
"uri": "https://api.corespeed.io/artifacts/…",
"expiresAt": "2026-09-10T14:02:11.000Z",
"width": 1024,
"height": 1024
}
]
}{ "job_id": "job_…", "status": "running", "model": "fal-ai/kling-video/v2" }A running job keeps going on the server for up to 60 minutes, then expires
unbilled. Collect it with media__get_result from any session — any agent in
the organization can pick up a job another one started — and pass
wait_seconds there to long-poll instead of spinning.
Three rules keep this from costing you twice:
- Do not set a client-side tool timeout equal to or below
wait_seconds. That kills the very reply carrying yourjob_id. Disable the timeout for this call, or set a backstop of at least twicewait_seconds. - If the client timed out anyway, nothing is lost. The newest entry in
media__list_jobsis your job. Do not resubmit. - Signed URLs expire after 12 hours. Call
media__get_resultagain for a fresh set; the stored artifact does not go anywhere.
Job states are running, succeeded, failed, canceled, and expired. A
terminal failure comes back as a tool-level error with generation_failed (or
empty_output when the upstream finished without a file), carrying the
upstream's reason in the text when it gave one; generation_canceled or
generation_expired; an unknown id answers job_not_found. Every code is in
Errors & status codes.
Speech and voices
The Fish Audio models in the catalog turn text into speech:
fish-audio/tts/drama-3 (directed performance), fish-audio/tts/s2.1-pro and
fish-audio/tts/s2-pro. Find them with media__list_models (query: "fish",
or kind: "audio"). Their input takes the script as text, with any inline
directions the model supports — [whispers], [shaky breath],
<emphasis>…</emphasis>.
Choose the voice one of two ways:
- A library voice.
media__list_voicessearches Fish Audio's public library by name and language; each row has asample_urlto listen to first. Pass itsvoice_id. Uselicensed_only: truefor commercial work: the rest of the library is community uploads, and some imitate real people. A licensed voice bills at a higher rate than the model alone. - Your own voice. Your own voice is an audio file in your Drive: a
recording of one speaker, 10–30 seconds of clean speech (up to 10 MB).
Pass it as
reference_audio_url, with the exact words it says asreference_text. Fish Audio keeps nothing: the file is sent with each call, so keep it short. It bills at the model's rate.
Only use a voice you have the speaker's permission to use.
For dialogue, start each turn with <|speaker:0|>, <|speaker:1|>, … and pass
one voice per speaker, in order: an array of voice_ids, or an array of
reference_audio_url files with an array of reference_texts, one each.
fish-audio/voice-design/v1 designs a new voice from a description and returns
up to four short samples in your Drive. To speak with one, pass its file as
reference_audio_url.
Transcribe: speech to text
media__transcribe takes one media source — a Drive path or file id, or a
public https URL — up to 60 minutes and 100 MB: WAV, MP3, AAC/M4A, FLAC, Ogg,
WebM or MOV. Send a whole conversation in one call: speaker labels are
consistent only within one answer.
The transcript is the text answer, with speakers marked inline
(<|speaker:0|>) and emotion and sound cues such as [laughter] unless
tag_audio_events is false. timestamps: true adds word-level segments and
timed speaker_turns. language is only a hint; the language is detected
either way. Long recordings take minutes to process.
Understand: ask about a file
media__understand takes a prompt and one media source — a file in your
Drive (its path or file id), a public https URL, or a YouTube watch URL.
mime_type is inferred from the URL extension or the origin's Content-Type,
or carried by the Drive file.
- Video can be windowed with
start_seconds/end_seconds.max_cost_creditsconverts a credit budget into a window at the per-second rate; it bounds the media input only, and an explicitend_secondsoverrides it. Video is sampled at one frame per second, so fast action between frames can be missed, and timestamps stated in the answer are not guaranteed exact. - Audio and images reject all three window parameters, because clipping cannot be enforced for them. The whole file is analysed up to the capability's context limit.
The result carries analyzed_seconds (an estimate — a paging hint, not a
duration), truncated when the answer was cut at the output limit, and usage
with the metered quantities the call settled for.
Upload a private file
For a file that only exists on local disk:
- Call
drive__create_uploadwith the exactmime_typeandsize_bytes. The result includesfile_id,upload_url, thePUTmethod, the headers to send — includingAuthorization—expires_at, andlands_at: the path the file will have in your Drive. PUTthe raw bytes toupload_urlwith every returned header, within 10 minutes. The ticket is one-use, and the declared type and size are bound to it.- Use the path (or
file_id) withmedia__understandor any file-typedmedia__generateinput. The file sits undertmp/uploads/for 7 days;drive__moveit anywhere else to keep it.
Every output a generation produces is a Drive file too, under
tmp/media/<job_id>/, for 7 days unless you move it.
Files up to 2 GB are accepted. A deployment edge may still reject a very large
body with HTTP 413; treat that as non-retryable for the deployment rather than
retrying the same upload.
Pricing and holds
- Generation is billed once, after success, at the model's rate times the
usage the upstream actually reports. For calls whose size is only known
afterwards, the final amount can differ from the rate card. Failures,
cancels, and expiries never charge. For expensive kinds — video especially —
check
media__get_modelfirst. - Speech bills per UTF-8 byte of
text, tags included, at the model's rate per 1,000 bytes — so a character in Chinese or Japanese costs three bytes. A licensed library voice adds a surcharge per 1,000 bytes on the text it speaks. Voice design bills once per call, however many samples it returns. - Transcription bills 9 credits per minute of audio, per second of the whole recording, silence included.
- Understanding bills 0.5 credits per image (an oversized image counts as several), 12 credits per minute of video and 4.2 per minute of audio, both metered per second, plus about 1.1 credits per 100 answer tokens. The question itself is free. Failures never charge.
The live figures are in the tool descriptions your client shows from
tools/list and in each model's price. Every charge lands itemized in the
organization ledger; see Billing & credits. An organization
billing hold or suspension stops a media call before it runs — as an isError
result with payment_required or org_suspended — and API-key spend caps apply
the same way.
Inspect in the dashboard
Dashboard → Media shows everything the
organization generated. Running jobs finish server-side and land there whichever
agent started them. Turning the capability off in
Capability controls hides the media__* tools from
tools/list; stored artifacts and job receipts are unaffected.