Медиа
Создать задачу
POST /api/v1/async/media/tasks — запуск генерации изображения, видео, аудио или музыки
POST
/
api
/
v1
/
async
/
media
/
tasks
Создать задачу генерации
curl --request POST \
--url https://speshu.ai/api/v1/async/media/tasks \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"model": "nano-banana-2",
"input": {},
"sessionId": "<string>",
"callBackUrl": "<string>"
}
'import requests
url = "https://speshu.ai/api/v1/async/media/tasks"
payload = {
"model": "nano-banana-2",
"input": {},
"sessionId": "<string>",
"callBackUrl": "<string>"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
model: 'nano-banana-2',
input: {},
sessionId: '<string>',
callBackUrl: '<string>'
})
};
fetch('https://speshu.ai/api/v1/async/media/tasks', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://speshu.ai/api/v1/async/media/tasks",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'model' => 'nano-banana-2',
'input' => [
],
'sessionId' => '<string>',
'callBackUrl' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://speshu.ai/api/v1/async/media/tasks"
payload := strings.NewReader("{\n \"model\": \"nano-banana-2\",\n \"input\": {},\n \"sessionId\": \"<string>\",\n \"callBackUrl\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://speshu.ai/api/v1/async/media/tasks")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"model\": \"nano-banana-2\",\n \"input\": {},\n \"sessionId\": \"<string>\",\n \"callBackUrl\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://speshu.ai/api/v1/async/media/tasks")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"model\": \"nano-banana-2\",\n \"input\": {},\n \"sessionId\": \"<string>\",\n \"callBackUrl\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"code": 200,
"msg": "success",
"data": {
"taskId": "<string>"
}
}{
"code": 123,
"msg": "<string>",
"data": {}
}{
"error": {
"message": "<string>",
"type": "invalid_request_error",
"param": "<string>",
"code": "invalid_request"
}
}{
"code": 123,
"msg": "<string>",
"data": {}
}{
"code": 123,
"msg": "<string>",
"data": {}
}{
"code": 123,
"msg": "<string>",
"data": {}
}{
"code": 123,
"msg": "<string>",
"data": {}
}{
"code": 123,
"msg": "<string>",
"data": {}
}{
"code": 123,
"msg": "<string>",
"data": {}
}{
"code": 123,
"msg": "<string>",
"data": {}
}{
"code": 123,
"msg": "<string>",
"data": {}
}{
"code": 123,
"msg": "<string>",
"data": {}
}{
"code": 123,
"msg": "<string>",
"data": {}
}{
"code": 123,
"msg": "<string>",
"data": {}
}Единая точка входа для всей генерации медиа. Она создаёт асинхронную задачу: вы выбираете модель, описываете параметры в объекте
Единственное исключение —
Неизвестные поля внутри
Максимальный размер тела запроса — 100 МБ.
Точный состав
Ссылка должна быть публично доступна без авторизации — её скачает генератор. Подойдёт и обычная прямая ссылка на файл в интернете, если провайдер дотянется до неё.
Подробнее о состояниях и о том, почему
До трёх попыток: первая сразу, повторы примерно через 15 и 30 секунд. Доставка «fire-and-forget» — запрос не подписывается, поэтому принимающая сторона должна защищаться сама. Недоставленное уведомление на задачу не влияет: опрос остаётся рабочим вариантом.
Разбор — на странице Ошибки.
input и получаете taskId, по которому опрашиваете задачу или скачиваете результат.
Стоимость списывается в момент создания, а не по завершении. Если отправка провайдеру не удалась, списание автоматически возвращается.
Конверт ответа
Все медиа-эндпоинты отвечают одним конвертом:code дублирует HTTP-статус, msg — человекочитаемый текст, data — полезная нагрузка (может быть null). При успехе code всегда равен 200, а msg — "success", даже если HTTP-статус равен 201.
{ "code": 200, "msg": "success", "data": { "taskId": "019..." } }
Это не формат ошибок текстовых эндпоинтов. Ошибка здесь выглядит как
{"code": 422, "msg": "…", "data": null}, а не как объект error. Разбор — на странице Ошибки.401: если ключ не прошёл проверку, ошибку формирует промежуточный слой, и она приходит в формате OpenAI.
Тело запроса
| Поле | Тип | Обязательное | Описание |
|---|---|---|---|
model | string | Да | Идентификатор из GET /api/v1/media/models |
input | object | Да | Параметры генерации. Состав и ограничения задаёт input_schema конкретной модели |
sessionId | string | Нет | Медиа-сессия из POST /api/v1/media/sessions |
callBackUrl | string | Нет | Адрес, на который придёт POST, когда задача дойдёт до финального состояния |
input игнорируются. Обязательные поля, перечисления, ограничения длины и количества, а также числовые диапазоны проверяются.
Поле
step (в схеме оно называется multipleOf) сервером не проверяется. Не рассчитывайте на него.Первый запрос
curl -X POST "https://speshu.ai/api/v1/async/media/tasks" \
-H "Authorization: Bearer $SPESHU_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"model": "google/nano-banana",
"input": {
"prompt": "Кот в библиотеке, тёплый свет, плёночное фото",
"aspect_ratio": "16:9"
}
}'
import os
import requests
response = requests.post(
"https://speshu.ai/api/v1/async/media/tasks",
headers={
"Authorization": f"Bearer {os.environ['SPESHU_API_KEY']}",
"Idempotency-Key": "my-unique-key",
},
json={
"model": "google/nano-banana",
"input": {
"prompt": "Кот в библиотеке, тёплый свет, плёночное фото",
"aspect_ratio": "16:9",
},
},
timeout=300,
)
task_id = response.json()["data"]["taskId"]
const res = await fetch("https://speshu.ai/api/v1/async/media/tasks", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SPESHU_API_KEY}`,
"Content-Type": "application/json",
"Idempotency-Key": crypto.randomUUID(),
},
body: JSON.stringify({
model: "google/nano-banana",
input: {
prompt: "Кот в библиотеке, тёплый свет, плёночное фото",
aspect_ratio: "16:9",
},
}),
});
const { data } = await res.json();
const taskId = data.taskId;
input, включая имя массива ссылок, смотрите в input_schema модели, а не в таблицах документации.
Идемпотентность
ЗаголовокIdempotency-Key длиной до 255 символов делает создание безопасным для повторов: повторный запрос с тем же ключом возвращает исходную задачу и не списывает средства повторно.
Не генерируйте новый ключ на каждый повтор. Новый ключ — это новая задача и повторное списание.
Изображение на входе
Если генерации нужно исходное изображение, сначала загрузите файл в хранилище и подставьте полученныйurl в input_urls. Загрузить файл можно в хранилище.
IMAGE_URL=$(curl -s -X POST "https://speshu.ai/api/v1/storage/upload" \
-H "Authorization: Bearer $SPESHU_API_KEY" \
-F "file=@photo.jpg" | jq -r '.url')
curl -X POST "https://speshu.ai/api/v1/async/media/tasks" \
-H "Authorization: Bearer $SPESHU_API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"model\": \"google/nano-banana-edit\",
\"input\": {
\"prompt\": \"Замени фон на офис\",
\"image_urls\": [\"$IMAGE_URL\"]
}
}"
import os
import requests
HEADERS = {"Authorization": f"Bearer {os.environ['SPESHU_API_KEY']}"}
with open("photo.jpg", "rb") as fh:
upload = requests.post(
"https://speshu.ai/api/v1/storage/upload",
headers=HEADERS, files={"file": fh}, timeout=300,
)
image_url = upload.json()["url"]
requests.post(
"https://speshu.ai/api/v1/async/media/tasks",
headers=HEADERS,
json={
"model": "google/nano-banana-edit",
"input": {"prompt": "Замени фон на офис", "image_urls": [image_url]},
},
timeout=300,
)
Полный цикл: создать и дождаться
Опрос статуса — основной контракт: создание возвращает толькоtaskId, результат приходит в поле resultJson объекта задачи.
Python
import json
import os
import time
import requests
BASE = "https://speshu.ai/api/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['SPESHU_API_KEY']}"}
created = requests.post(
f"{BASE}/async/media/tasks",
headers=HEADERS,
json={
"model": "bytedance/seedance-2",
"input": {
"prompt": "Морской берег в шторм, кинематографично",
"aspect_ratio": "16:9",
"duration": 5,
},
},
timeout=300,
).json()
task_id = created["data"]["taskId"]
while True:
time.sleep(5)
task = requests.get(
f"{BASE}/async/media/tasks/{task_id}", headers=HEADERS, timeout=30
).json()["data"]
if task["state"] == "waiting":
continue
if task["state"] == "fail":
raise RuntimeError(f'{task["failCode"]}: {task["failMsg"]}')
# resultJson — строка, её нужно распарсить
print(json.loads(task["resultJson"])["resultUrls"])
break
state и фильтр status выглядят по-разному, — на странице Статус задачи.
Уведомление вместо опроса
Если задатьcallBackUrl, платформа отправит POST на этот адрес, когда задача дойдёт до финального состояния. Тело — тот же конверт с заполненным data:
{
"code": 200,
"msg": "success",
"data": {
"taskId": "019...",
"model": "bytedance/seedance-2",
"state": "success",
"resultJson": "{\"resultUrls\": [\"https://...\"]}",
"createTime": 1715000000000
}
}
Коды ответа
| Статус | Когда |
|---|---|
200 | Задача создана |
400 | Некорректное тело запроса или ошибка отправки провайдеру |
401 | API-ключ не прошёл проверку (формат ошибки — OpenAI) |
402 | Недостаточно средств |
403 | Возможность недоступна на вашем тарифе |
404 | Модель неизвестна или отключена; sessionId не найден, архивен или удалён |
409 | Сессия занята |
422 | Ошибка валидации input — сообщение называет проблемное поле |
429 | Превышен частотный лимит |
499 | Клиент прервал запрос |
500 | Внутренняя ошибка |
502 | Провайдер настроен некорректно |
503 | Провайдер недоступен |
504 | Таймаут |
Статус задачи
Поля объекта задачи и состояния
Жизненный цикл задачи
Скачивание, архив, удаление, восстановление
Каталог медиа-моделей
Схемы параметров и цены
Медиа-сессии
Группировка задач
Авторизации
API-ключ в формате sk-.... Альтернативно — заголовок X-Api-Key: sk-... (совместимо с Anthropic SDK).
Заголовки
Ключ идемпотентности (максимум 255 символов). Повторный запрос с тем же ключом возвращает исходную задачу и не списывает средства повторно.
Maximum string length:
255Тело
application/json
Идентификатор модели из GET /api/v1/media/models.
Пример:
"nano-banana-2"
Параметры модели. Состав и ограничения описаны в input_schema модели.
Идентификатор медиа-сессии из POST /api/v1/media/sessions.
Адрес, на который придёт POST с результатом, когда задача дойдёт до финального состояния. Не подписывается.
⌘I
Создать задачу генерации
curl --request POST \
--url https://speshu.ai/api/v1/async/media/tasks \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"model": "nano-banana-2",
"input": {},
"sessionId": "<string>",
"callBackUrl": "<string>"
}
'import requests
url = "https://speshu.ai/api/v1/async/media/tasks"
payload = {
"model": "nano-banana-2",
"input": {},
"sessionId": "<string>",
"callBackUrl": "<string>"
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
model: 'nano-banana-2',
input: {},
sessionId: '<string>',
callBackUrl: '<string>'
})
};
fetch('https://speshu.ai/api/v1/async/media/tasks', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://speshu.ai/api/v1/async/media/tasks",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'model' => 'nano-banana-2',
'input' => [
],
'sessionId' => '<string>',
'callBackUrl' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://speshu.ai/api/v1/async/media/tasks"
payload := strings.NewReader("{\n \"model\": \"nano-banana-2\",\n \"input\": {},\n \"sessionId\": \"<string>\",\n \"callBackUrl\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://speshu.ai/api/v1/async/media/tasks")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"model\": \"nano-banana-2\",\n \"input\": {},\n \"sessionId\": \"<string>\",\n \"callBackUrl\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://speshu.ai/api/v1/async/media/tasks")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"model\": \"nano-banana-2\",\n \"input\": {},\n \"sessionId\": \"<string>\",\n \"callBackUrl\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"code": 200,
"msg": "success",
"data": {
"taskId": "<string>"
}
}{
"code": 123,
"msg": "<string>",
"data": {}
}{
"error": {
"message": "<string>",
"type": "invalid_request_error",
"param": "<string>",
"code": "invalid_request"
}
}{
"code": 123,
"msg": "<string>",
"data": {}
}{
"code": 123,
"msg": "<string>",
"data": {}
}{
"code": 123,
"msg": "<string>",
"data": {}
}{
"code": 123,
"msg": "<string>",
"data": {}
}{
"code": 123,
"msg": "<string>",
"data": {}
}{
"code": 123,
"msg": "<string>",
"data": {}
}{
"code": 123,
"msg": "<string>",
"data": {}
}{
"code": 123,
"msg": "<string>",
"data": {}
}{
"code": 123,
"msg": "<string>",
"data": {}
}{
"code": 123,
"msg": "<string>",
"data": {}
}{
"code": 123,
"msg": "<string>",
"data": {}
}