Models and media

List the chat models your key may use, then generate images, video, speech and music, and transcribe audio. Image generation answers straight away; the other media jobs run in the background and you poll them.

8 min read · Updated 28 September 2026

This page covers the model catalogue and every media endpoint. Chat models are what you pass as ai_model_id to conversations and runs. Video, speech, music and transcription each have their own model catalogue and follow one shared job pattern.

All paths are relative to https://api.stickyprompts.com/api/v1 and every request carries Authorization: Bearer $STICKY_API_KEY.

Conventions

  • IDs and slugs. Every generation and transcription has a numeric ID and an 8-character slug. GET /<resource>/:id and POST /<resource>/:id/retry accept either. DELETE /<resource>/:id accepts only the numeric ID.
  • Decimals are strings. Credit prices and costs (credit_cost, credit_cost_per_*, multiplier) come back as JSON strings such as "0.000015".
  • Field casing. Records show ID, CreatedAt, UpdatedAt, DeletedAt in PascalCase next to snake_case fields. See Errors, pagination and quotas.
  • Lists are paginated with page (default 1) and size (default 10), newest first, with X-Total-Count, X-Page and X-Page-Size headers. The body is a bare array.
  • Quota. Routes that start paid work answer 429 {"error":"Quota exceeded","reason":"..."} when the plan is out of room.
  • Restricted models. If a workspace rule blocks the model you chose, create calls answer 403 {"error":"the selected model has been restricted by your organization"}.

Chat models

Chat models are identified by their numeric ID. name is a display name, not an identifier. With an API key, your workspace’s model access rules and data residency policy are applied: models you cannot use come back with selectable: false and a restriction_reason.

GET /ai_model

GET /api/v1/ai_model

Lists the chat-model catalogue in display order. Deprecated models are included unless you pass legacy=false.

Query parameters
search string query optional
Substring match on name.
powered_by string query optional
Exact match on the maker label, for example Anthropic. Values come from GET /ai_model/filters.
tools comma list query optional
Model must have all listed capabilities, for example web_search,file_upload.
tags comma list query optional
Model must have all listed tags, for example coding.
flagship true query optional
Only flagship models.
legacy false query optional
Hide deprecated models.
page, size integer query optional
Optional. Without them, every matching model is returned.
curl "https://api.stickyprompts.com/api/v1/ai_model?legacy=false&flagship=true" \
  -H "Authorization: Bearer $STICKY_API_KEY"
import os, requests

r = requests.get(
    "https://api.stickyprompts.com/api/v1/ai_model",
    params={"legacy": "false", "flagship": "true"},
    headers={"Authorization": f"Bearer {os.environ['STICKY_API_KEY']}"},
)
usable = [m for m in r.json() if m["selectable"]]
for m in usable:
    print(m["ID"], m["name"], m["multiplier"])
const res = await fetch(
  "https://api.stickyprompts.com/api/v1/ai_model?legacy=false&flagship=true",
  { headers: { Authorization: `Bearer ${process.env.STICKY_API_KEY}` } },
);
const models = (await res.json()).filter((m) => m.selectable);
console.log(models.map((m) => [m.ID, m.name]));

Response 200 (one element shown, values illustrative):

[
  {
    "ID": 42,
    "name": "Claude Sonnet 5",
    "description": "Balanced model for everyday work.",
    "powered_by": "Anthropic",
    "version": "claude-sonnet-x",
    "multiplier": "1.6",
    "credit_cost_per_input_token": "0.0003",
    "credit_cost_per_output_token": "0.0015",
    "flagship": true,
    "premium": false,
    "deprecated": false,
    "thinking_model": true,
    "reasoning_efforts": ["low", "medium", "high"],
    "default_reasoning_effort": "medium",
    "tools": ["file_upload", "web_search", "remote_mcp"],
    "tags": ["analysis", "coding"],
    "best_for": "Writing, analysis and code",
    "knowledge_cutoff": "2026-01-01T00:00:00Z",
    "supported_file_extensions": [],
    "successor": null,
    "selectable": true
  }
]

Useful fields: tools lists capabilities (file_upload, file_generation, image_generation, web_search, web_fetch, code_execution, remote_mcp and more), reasoning_efforts lists the allowed reasoning tiers, successor names the replacement for a deprecated model, and supported_file_extensions restricts attachments (empty means no restriction). The catalogue has no context-window size, USD price or benchmark field.

GET /ai_model/:id

GET /api/v1/ai_model/:id
Permission ai_models_get

Returns one model by numeric ID. successor is not loaded here.

GET /ai_model/filters

GET /api/v1/ai_model/filters
Permission ai_models_get

Returns the distinct values you can filter the catalogue by:

{
  "powered_by": ["Anthropic", "Google", "OpenAI", "xAI"],
  "tools": ["code_execution", "file_upload", "image_generation", "web_search"],
  "tags": ["analysis", "coding", "creative_writing", "reasoning"]
}

A list is null rather than [] when no model has a value for it.

GET /ai_model/restricted

GET /api/v1/ai_model/restricted
Permission ai_models_get

Deprecated. It only reports models blocked by a model-level rule in your workspace. Use the selectable flag from GET /ai_model instead.

Image generation

Image generation is synchronous: the call returns once the image exists. The server uses the image model StickyPrompts has configured; the prompt is the only control (no size, count or aspect parameters).

POST /image_generation

POST /api/v1/image_generation
Body
prompt string required
What to create.
curl -X POST https://api.stickyprompts.com/api/v1/image_generation \
  -H "Authorization: Bearer $STICKY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"prompt": "Product photo of a laptop on an oak desk showing a retail dashboard with yellow charts, morning light"}'
import os, requests

r = requests.post(
    "https://api.stickyprompts.com/api/v1/image_generation",
    headers={"Authorization": f"Bearer {os.environ['STICKY_API_KEY']}"},
    json={"prompt": "Product photo of a laptop on an oak desk showing a retail dashboard"},
    timeout=180,
)
image = r.json()
print(image["ID"], image["path"])
const res = await fetch("https://api.stickyprompts.com/api/v1/image_generation", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.STICKY_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ prompt: "Product photo of a laptop on an oak desk showing a retail dashboard" }),
});
const image = await res.json();
console.log(image.ID, image.path);

Response 200, a file record:

{
  "ID": 9812,
  "name": "image_0.png",
  "path": "https://<storage-host>/<bucket>/123/Xk3...q9.png",
  "user_id": 123,
  "fileType": "code_interpreter_output",
  "partial": false
}

The image is at path, a direct storage URL. Keep the ID: it works as file_id for editing, as a video starting frame, and in the file endpoints. Note the camel-case fileType.

Errors: 400 without prompt; 500 {"error":"image generation produced no output file"} when the model returns nothing; 500 with the message when the model fails or data residency blocks the provider. Allow a generous client timeout.

POST /image_generation/edit

POST /api/v1/image_generation/edit

Edits an image you own and returns a new file. The source file is not changed.

Body
prompt string required
The edit, for example “Change the sweater to deep forest green. Keep everything else the same.”
file_id integer required
A file you own: an earlier generation or an upload from POST /file.

Response 200: a new file record. 404 {"error":"file not found or access denied"} if the file isn’t yours.

Background jobs: video, speech, music, transcription

These four resources share one pattern:

  1. Pick a model

    GET /<kind>_model returns that kind’s catalogue. Choose a model by numeric ID. The default: true flag marks the app’s preselected model, but the API does not fall back to it: always send the model ID.

  2. Create the job

    POST /<kind> validates the request, checks quota and access, and answers 201 with the job in status queued.

  3. Poll until it finishes

    GET /<kind>/:id until status is done or error. There are no webhooks or event streams for these jobs; poll every few seconds.

  4. Collect the result

    Video, speech and music: the file is at output_file_data.path. Transcription: text and segments are in the record itself.

import os, time, requests

API = "https://api.stickyprompts.com/api/v1"
H = {"Authorization": f"Bearer {os.environ['STICKY_API_KEY']}"}

job = requests.post(f"{API}/music_generation", headers=H, json={
    "music_generation_model_id": 7,          # from GET /music_generation_model
    "prompt": "Upbeat product-launch track, electric piano, 112 BPM, no vocals",
    "duration_seconds": 30,
    "instrumental": True,
}).json()

while job["status"] not in ("done", "error"):
    time.sleep(5)
    job = requests.get(f"{API}/music_generation/{job['slug']}", headers=H).json()

print(job["status"], job.get("output_file_data", {}).get("path"), job["credit_cost"])
const API = "https://api.stickyprompts.com/api/v1";
const H = { Authorization: `Bearer ${process.env.STICKY_API_KEY}`, "Content-Type": "application/json" };

let job = await (await fetch(`${API}/music_generation`, {
  method: "POST",
  headers: H,
  body: JSON.stringify({
    music_generation_model_id: 7, // from GET /music_generation_model
    prompt: "Upbeat product-launch track, electric piano, 112 BPM, no vocals",
    duration_seconds: 30,
    instrumental: true,
  }),
})).json();

while (!["done", "error"].includes(job.status)) {
  await new Promise((r) => setTimeout(r, 5000));
  job = await (await fetch(`${API}/music_generation/${job.slug}`, { headers: H })).json();
}
console.log(job.status, job.output_file_data?.path, job.credit_cost);

Rules shared by all four:

  • The model ID is required. Omitting it or sending an unknown ID returns a generic 500 "Failed to create ...", not a 400.
  • Cost. GET /<kind>/:id includes credit_cost, the credits charged for the job. List responses always show "0" there.
  • Data residency. If your workspace’s residency policy forbids the model’s provider, the job is accepted and then fails with the policy message in error. Models like that show selectable: false in the catalogue, so check first.
  • Retry. POST /<kind>/:id/retry re-queues a job in status error (otherwise 400 "Only failed ... can be retried"), re-checking access and quota.
  • Delete. DELETE /<kind>/:id (numeric ID) returns 204, also when nothing matched. It is a soft delete and does not remove the output file.
  • Filter lists with ?status=<value>.

Every media model has ID, name, powered_by, version, description, multiplier, default, sort, selectable and, when relevant, restriction_reason. Catalogues are not paginated.

Video generation

Status: queued, then processing, then done or error.

GET /video_generation_model

GET /api/v1/video_generation_model

Video models add: supported_aspect_ratios (empty means any), supported_resolutions (first is the default), resolution_duration_limits (per resolution, "*" as fallback, {min_duration_seconds, max_duration_seconds} with 0 meaning unbounded), resolution_pricing (credit_cost_per_second per resolution), interpolation_support (none, start_only or full), max_reference_images and reference_types.

POST /video_generation

POST /api/v1/video_generation
Body
video_generation_model_id integer required
Model ID.
prompt string required
The scene to generate.
aspect_ratio string optional default 16:9
Must be in the model’s supported_aspect_ratios when that list is not empty.
resolution string optional
Defaults to the model’s first supported resolution.
duration_seconds integer optional
Defaults to the maximum for the resolution. Must be within its limits.
starting_image_file_id integer optional
An image you own, as the first frame. The model must support start_only or full.
last_frame_file_id integer optional
Needs a starting image and a model with full support.
reference_image_ids integer[] optional
Images you own, up to max_reference_images. Cannot be combined with starting or last frames.
curl -X POST https://api.stickyprompts.com/api/v1/video_generation \
  -H "Authorization: Bearer $STICKY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "video_generation_model_id": 4,
    "prompt": "Slow dolly shot across a bright retail store at opening time, warm morning light",
    "aspect_ratio": "16:9",
    "resolution": "720p",
    "duration_seconds": 8
  }'

Response 201 (trimmed):

{
  "ID": 311,
  "slug": "a8Kd02Lq",
  "video_generation_model_id": 4,
  "prompt": "Slow dolly shot across a bright retail store at opening time, warm morning light",
  "aspect_ratio": "16:9",
  "resolution": "720p",
  "duration_seconds": 8,
  "status": "queued",
  "error": "",
  "content_policy_violation": false,
  "output_file_data_id": null,
  "credit_cost": "0"
}

When done, output_file_data.path is the .mp4 and actual_duration_seconds and credit_cost are filled in. content_policy_violation: true means the provider’s safety filter refused the request.

Validation errors are 400 with a precise message, for example "resolution is not supported by this video generation model", "requested duration exceeds this video generation model's maximum", "a last frame image requires a starting image" or "attached file must be an image". Using someone else’s file returns 403 "you do not have permission to use this file".

GET /video_generation, GET /video_generation/:id

GET /api/v1/video_generation/:id

List your video jobs (page, size, status) or fetch one by ID or slug, including credit_cost. 404 {"error":"Video generation not found"}.

POST /video_generation/:id/retry

POST /api/v1/video_generation/:id/retry

DELETE /video_generation/:id

DELETE /api/v1/video_generation/:id

Text to speech

Status: queued, then processing, then done or error.

GET /tts_generation_model

GET /api/v1/tts_generation_model

Speech models add supported_languages (empty means any) and a tts_voice_set whose voices each have a voice_id (the value to send as voice), name, gender, language, accent, description, preview_audio_url and default.

POST /tts_generation

POST /api/v1/tts_generation
Body
tts_generation_model_id integer required
Model ID.
text string required
What to say.
voice string optional
A voice_id from the model’s voice set. Defaults to the voice marked default, otherwise the first.
language string optional default auto
Must be in supported_languages when that list is not empty.

Answers 201 with the job. When done, the audio (.wav or .mp3, whichever the provider returned) is at output_file_data.path. Errors: 400 "voice is not supported by this tts generation model", 400 "language is not supported by this tts generation model".

GET /tts_generation, GET /tts_generation/:id

GET /api/v1/tts_generation/:id

POST /tts_generation/:id/retry

POST /api/v1/tts_generation/:id/retry

DELETE /tts_generation/:id

DELETE /api/v1/tts_generation/:id

Music generation

Status: queued, then processing, then done or error.

GET /music_generation_model

GET /api/v1/music_generation_model

Music models add min_duration_seconds and max_duration_seconds (0 means unbounded), supported_output_formats (first is the default), credit_cost_per_generation and credit_cost_per_second.

POST /music_generation

POST /api/v1/music_generation
Body
music_generation_model_id integer required
Model ID.
prompt string required
Style, instruments, tempo, mood.
duration_seconds integer optional
Defaults to the model’s maximum. Must be within its limits.
output_format string optional
Defaults to the first supported format.
instrumental boolean optional default false
No vocals when true.

When done, the track is at output_file_data.path and any lyrics are in lyrics. The file extension follows what the provider actually returned, so it can differ from output_format. Errors: 400 for a duration outside the limits or an unsupported format.

GET /music_generation, GET /music_generation/:id

GET /api/v1/music_generation/:id

POST /music_generation/:id/retry

POST /api/v1/music_generation/:id/retry

DELETE /music_generation/:id

DELETE /api/v1/music_generation/:id

Transcription

Status: queued, fetching, preparing, processing, then done or error. status_progress runs from 0 to 100; it moves in steps only for models that process audio in chunks.

GET /transcription_model

GET /api/v1/transcription_model

Transcription models add response_format (only diarized_json models label speakers), supported_languages and credit prices per token and per second.

POST /transcription

POST /api/v1/transcription

There is no multipart upload on this route: upload the audio or video with POST /file first, or give a URL. For video files the audio track is extracted for you.

Body
transcription_model_id integer required
Model ID.
file_data_id integer optional
An uploaded audio or video file. Send this or source_url, not both.
source_url string optional
A publicly downloadable URL, fetched with a plain GET.
language string optional
A hint. Omit it to auto-detect; the detected language is returned in language.

A finished record (trimmed):

{
  "ID": 77,
  "slug": "Qm7tR2xa",
  "status": "done",
  "status_progress": 100,
  "text": "Hi team, quick update before Tuesday's launch...",
  "segments": [
    { "start": 0.0, "end": 28.4, "text": "Hi team, quick update before Tuesday's launch...", "speaker": "speaker_0" },
    { "start": 29.0, "end": 37.0, "text": "Thanks Darian. One question from support...", "speaker": "speaker_1" }
  ],
  "speakers": [
    { "label": "speaker_0", "name": "", "color": "#F59E0B" },
    { "label": "speaker_1", "name": "", "color": "#10B981" }
  ],
  "language": "en",
  "duration_seconds": 42.1,
  "credit_cost": "4.22"
}

Adjacent segments from the same speaker are merged, and a gap of two seconds or more appears as its own segment with a pause value and empty text. speaker is null for models that don’t separate speakers.

Errors: 400 "Either file_data_id or source_url is required", 400 "Can't set both file_data_id and source_url", 400 "language is not supported by this transcription model".

GET /transcription, GET /transcription/:id

GET /api/v1/transcription/:id

List your transcriptions (page, size, status) or fetch one by ID or slug, including credit_cost. Don’t send a workspace_id query parameter to the list.

PATCH /transcription/:id/speakers

PATCH /api/v1/transcription/:id/speakers

Rename or recolour speakers, the same as Speakers in the app. Only the fields you send change; an unknown label is added as a new speaker.

{
  "speakers": {
    "speaker_0": { "name": "Darian", "color": "#3B82F6" },
    "speaker_1": { "name": "Sam" }
  }
}

Returns 200 with the full transcription.

POST /transcription/:id/retry

POST /api/v1/transcription/:id/retry

DELETE /transcription/:id

DELETE /api/v1/transcription/:id

Usage statistics

The /statistics/* and /credits/* routes need statistics_get, a permission only StickyPrompts staff can grant. Customer keys cannot call them. Your workspace’s usage is available in the app under Usage, credits and billing, and every media job reports its own credit_cost.