API reference
Async tasks
Image, video, and music jobs share one durable lifecycle: submit once, persist task_id, observe a terminal state, then fetch the result. Build one task runner and select only the endpoint family per model.
#One lifecycle for every async model
- 1
Submit once
Use a unique Idempotency-Key and save the response immediately.
POST /v1/{capability} - 2
Persist task_id
Store it before the worker acknowledges the job.
task_id - 3
Poll or receive webhook
Compare status case-insensitively and stop at a terminal state.
GET /{task_id} - 4
Fetch and persist result
Call /content after success and store the returned platform URL.
GET /{task_id}/content
#Endpoint families
| Capability | Submit | Poll | Content |
|---|---|---|---|
| Image | /v1/images/tasks | /v1/images/tasks/{task_id} | /v1/images/tasks/{task_id}/content |
| Video | /v1/videos | /v1/videos/{task_id} | /v1/videos/{task_id}/content |
| Music | /v1/music/generations | /v1/music/tasks/{task_id} | /v1/music/tasks/{task_id}/content |
#Status values
One lifecycle, two wire vocabularies. Image, music and 3D tasks report the OpenAI task words; videos and every webhook payload report the OpenAI video words. A status check written for one family silently never matches in the other — this table is the mapping.
| Lifecycle | Image · music · 3D | Video · webhook | Terminal | Meaning |
|---|---|---|---|---|
| NOT_START | processing | queued | no | Accepted by the gateway, not yet handed to a channel. |
| SUBMITTED | queued | queued | no | Sent upstream and acknowledged by the vendor. |
| QUEUED | queued | queued | no | Waiting in the vendor’s queue. |
| IN_PROGRESS | processing | in_progress | no | Rendering. progress carries a percentage string such as "60%". |
| SUCCESS | succeeded | completed | yes | Finished. Fetch the result from /content. |
| FAILURE | failed | failed | yes | Failed for good. fail_reason explains why; the reserved quota is refunded. |
| UNKNOWN | unknown | unknown | no | The vendor stopped reporting a state the gateway can map. Keep polling with a deadline, then reconcile — do not treat it as success or failure. |
Match on the terminal set, never on “not running”
UNKNOWN is not a failure
#Safe polling loop
Stop on the terminal words for the family you are polling, keep a wall-clock deadline for everything else, and back off so a stuck task cannot turn into a hot loop.
import time, requests
# The two vocabularies. Pick the one that matches the endpoint you poll.
TASK_DONE, TASK_FAILED = {"succeeded"}, {"failed"} # images / music / 3D
VIDEO_DONE, VIDEO_FAILED = {"completed"}, {"failed"} # videos + webhooks
DEADLINE_SECONDS = 15 * 60
def wait_for_image_task(task_id, api_key):
started = time.monotonic()
delay = 2
while True:
response = requests.get(
f"https://www.aisplice.store/v1/images/tasks/{task_id}",
headers={"Authorization": f"Bearer {api_key}"},
timeout=30,
)
response.raise_for_status()
task = response.json()
status = task.get("status", "")
if status in TASK_DONE:
return task
if status in TASK_FAILED:
raise RuntimeError((task.get("error") or {}).get("message", "task failed"))
# queued / processing / unknown all mean "not finished". Wait — but
# never forever: hand an overdue task to reconciliation.
if time.monotonic() - started > DEADLINE_SECONDS:
raise TimeoutError(f"task {task_id} still {status} after the deadline")
time.sleep(delay)
delay = min(delay * 1.5, 15)Resume after restarts
#Result retention
Fetch /content only after success. Result files are kept for 3 days after the task finishes; after that /content answers 410 with the code media_expired. Download a copy within 3 days if you need to keep it.
Still stuck?
Send us the request ID or the error message and we will look into it.