Requests and responses
Rules shared by every REST endpoint. The endpoint reference has each operation's exact fields and exceptions.
Base URL
The base URL is https://api.masko.ai/v1. Reference paths already include /v1, so append them to https://api.masko.ai without doubling it.
Send Authorization: Bearer $MASKO_API_KEY on protected requests and Content-Type: application/json for JSON bodies (multipart uploads use their documented type). See API keys and workspaces.
Response envelopes
Successful JSON responses put the result in data:
{
"data": {
"id": "RESOURCE_ID",
"name": "My mascot"
}
}Errors return error.code, error.message and optional error.details, so check the HTTP status before reading data (errors and recovery). A 204 has no body, /v1/openapi.json returns the specification directly, and /v1/docs returns documentation text.
Request IDs and versions
Every response carries a Request-Id header such as req_3f9c... (repeated as error.request_id; include it when you contact support) and the Masko-API-Version it used. Routes shared with the older API version, such as /v1/jobs, /v1/assets and /v1/canvas-templates, return older field names unless you send Masko-API-Version: 2026-09-26 or ?api_version=2026-09-26.
Pagination
limit is 1 to 100 (default 50). While meta.pagination.has_more is true, pass meta.pagination.next_cursor back exactly as cursor. offset also works; send either cursor or offset, not both.
{
"data": [],
"meta": {
"pagination": {
"total": 120,
"limit": 50,
"offset": 0,
"has_more": true,
"next_cursor": "c1_eyJvIjo1MH0"
}
}
}Generation receipts
Generation requests normally return 202 Accepted with a job receipt:
{
"data": {
"job_id": "JOB_ID",
"poll_url": "/v1/jobs/JOB_ID"
}
}Save the receipt, then poll GET /v1/jobs/JOB_ID until completed or failed, or use a webhook. Some poll_url values are masko.ai paths (/api/v1/...); on https://api.masko.ai, build https://api.masko.ai/v1/jobs/JOB_ID?api_version=2026-09-26 from the ID, as in the quickstart. Batch responses contain several jobs, as documented per endpoint.
?wait=true&timeout=120 long-polls up to 120 seconds and returns the current state on timeout without cancelling. A file URL can exist before its file is ready, and derived formats can finish after the parent job, so check the required assets before playback; see jobs and polling.
Costs and retries
Always send animation_model: "standard" costs 2 credits per second and "premium" 6, while leaving it out bills the older rate of 5 (credits and costs).
A client timeout does not cancel accepted generation. Authenticated resource POST endpoints accept Idempotency-Key, except /v1/auth/* and /v1/admin/*. Send a unique value of up to 255 characters, ideally a UUID, and save it with the request before sending:
curl -X POST https://api.masko.ai/v1/mascots/MASCOT_ID/generate \
-H "Authorization: Bearer masko_YOUR_API_KEY" \
-H "Idempotency-Key: 5f2c0b1e-8d3a-4c6f-9b2e-1a7d4e9c0f11" \
-H "Content-Type: application/json" \
-d '{"type": "image", "name": "Wave", "image_prompt": "waving hello"}'- A key belongs to your account and binds the credential workspace, API version, URL, content type and exact body bytes, including multipart boundaries. Reusing it with different inputs returns
409 idempotency_key_reused. - Once execution starts, its response (
5xxincluded) is stored for 24 hours after completion. A retry returns it withIdempotent-Replayed: true, without running or charging again; each attempt still gets its ownRequest-Id. - Replays need a valid write credential and current access to the original resources, or return
403. - A running request returns
409 idempotency_request_in_progress. If its outcome cannot be saved, the first response is503 idempotency_persistence_failed, later ones may be that409, and the key stays blocked even after 24 hours. Retry with the same key; check the saved job or contact support if it persists. - A stored
500does not prove nothing was written, so check the outcome before switching keys. After a completed receipt expires, reusing the key starts a new request.
Without the header, every POST runs as a new request.
