202 Accepted com job_id — consulte o status até completed.
Enfileirar
curl -X POST https://api.shappire.tools/v1/media/download \
-H "X-API-Key: $SHAPPIRE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"media_id":"med_abc123","format":"video_best"}'
const API_KEY = process.env.SHAPPIRE_API_KEY;
const BASE = 'https://api.shappire.tools/v1';
export async function enqueueDownload(mediaId, format) {
const res = await fetch(`${BASE}/media/download`, {
method: 'POST',
headers: {
'X-API-Key': API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({ media_id: mediaId, format }),
});
if (res.status !== 202) {
const err = await res.json();
throw new Error(err.error?.message ?? res.statusText);
}
const { data } = await res.json();
return data.job_id;
}
import os
import requests
API_KEY = os.environ["SHAPPIRE_API_KEY"]
BASE = "https://api.shappire.tools/v1"
def enqueue_download(media_id: str, format: str) -> str:
response = requests.post(
f"{BASE}/media/download",
headers={"X-API-Key": API_KEY},
json={"media_id": media_id, "format": format},
timeout=30,
)
response.raise_for_status()
return response.json()["data"]["job_id"]
func EnqueueDownload(mediaID, format string) (string, error) {
body, _ := json.Marshal(map[string]string{
"media_id": mediaID,
"format": format,
})
req, err := http.NewRequest(http.MethodPost, baseURL+"/media/download", bytes.NewReader(body))
if err != nil {
return "", err
}
req.Header.Set("X-API-Key", os.Getenv("SHAPPIRE_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
var out struct {
Data struct {
JobID string `json:"job_id"`
} `json:"data"`
}
if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
return "", err
}
return out.Data.JobID, nil
}
function enqueueDownload(string $mediaId, string $format): string {
global $apiKey, $base;
$ch = curl_init("$base/media/download");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"X-API-Key: $apiKey",
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'media_id' => $mediaId,
'format' => $format,
]),
]);
$body = curl_exec($ch);
curl_close($ch);
return json_decode($body, true)['data']['job_id'];
}
def enqueue_download(media_id, format)
uri = URI("#{BASE}/media/download")
req = Net::HTTP::Post.new(uri)
req['X-API-Key'] = API_KEY
req['Content-Type'] = 'application/json'
req.body = { media_id: media_id, format: format }.to_json
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
JSON.parse(res.body)['data']['job_id']
end
public String enqueueDownload(String mediaId, String format) throws Exception {
String body = String.format(
"{\"media_id\":\"%s\",\"format\":\"%s\"}", mediaId, format);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(BASE + "/media/download"))
.header("X-API-Key", apiKey)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
return response.body();
}
Polling
curl https://api.shappire.tools/v1/jobs/job_xyz789 \
-H "X-API-Key: $SHAPPIRE_API_KEY"
const API_KEY = process.env.SHAPPIRE_API_KEY;
const BASE = 'https://api.shappire.tools/v1';
export async function waitForJob(jobId, { intervalMs = 3000, timeoutMs = 300000 } = {}) {
const started = Date.now();
while (Date.now() - started < timeoutMs) {
const res = await fetch(`${BASE}/jobs/${jobId}`, {
headers: { 'X-API-Key': API_KEY },
});
const { data } = await res.json();
if (data.status === 'completed') return data;
if (data.status === 'failed') throw new Error(data.error?.message ?? 'job failed');
await new Promise((r) => setTimeout(r, intervalMs));
}
throw new Error('job timeout');
}
import time
def wait_for_job(job_id: str, interval: float = 3.0, timeout: float = 300.0) -> dict:
started = time.time()
while time.time() - started < timeout:
response = requests.get(
f"{BASE}/jobs/{job_id}",
headers={"X-API-Key": API_KEY},
timeout=30,
)
response.raise_for_status()
data = response.json()["data"]
if data["status"] == "completed":
return data
if data["status"] == "failed":
raise RuntimeError(data.get("error", {}).get("message", "job failed"))
time.sleep(interval)
raise TimeoutError("job timeout")
func WaitForJob(jobID string) (map[string]interface{}, error) {
for {
req, _ := http.NewRequest(http.MethodGet, baseURL+"/jobs/"+jobID, nil)
req.Header.Set("X-API-Key", os.Getenv("SHAPPIRE_API_KEY"))
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var out struct {
Data map[string]interface{} `json:"data"`
}
json.NewDecoder(resp.Body).Decode(&out)
status, _ := out.Data["status"].(string)
switch status {
case "completed":
return out.Data, nil
case "failed":
return nil, fmt.Errorf("job failed")
}
time.Sleep(3 * time.Second)
}
}
function waitForJob(string $jobId, int $timeoutSeconds = 300): array {
global $apiKey, $base;
$started = time();
while (time() - $started < $timeoutSeconds) {
$ch = curl_init("$base/jobs/$jobId");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ["X-API-Key: $apiKey"],
]);
$body = curl_exec($ch);
curl_close($ch);
$data = json_decode($body, true)['data'];
if ($data['status'] === 'completed') return $data;
if ($data['status'] === 'failed') throw new RuntimeException('job failed');
sleep(3);
}
throw new RuntimeException('job timeout');
}
def wait_for_job(job_id, interval: 3, timeout: 300)
started = Time.now
loop do
uri = URI("#{BASE}/jobs/#{job_id}")
req = Net::HTTP::Get.new(uri)
req['X-API-Key'] = API_KEY
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
data = JSON.parse(res.body)['data']
return data if data['status'] == 'completed'
raise 'job failed' if data['status'] == 'failed'
raise 'job timeout' if Time.now - started > timeout
sleep interval
end
end
public String getJob(String jobId) throws Exception {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(BASE + "/jobs/" + jobId))
.header("X-API-Key", apiKey)
.GET()
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
return response.body();
}
Resposta inicial (202)
{
"data": {
"job_id": "job_x9y8z7",
"status": "queued"
}
}
Job concluído
{
"data": {
"id": "job_x9y8z7",
"status": "completed",
"progress": 100,
"result": {
"download_url": "https://...",
"filename": "video.mp4",
"mime_type": "video/mp4",
"size_bytes": null,
"expires_at": "2026-09-15T12:00:00.000Z"
}
}
}
size_bytes é atualmente sempre null. Use filename e mime_type para identificar o arquivo.Baixar o arquivo
curl -L -OJ "URL_ASSINADA_DA_RESPOSTA"
URLs assinadas expiram em
result.expires_at (TTL padrão: 1 hora). Não armazene como link permanente.Limite de concorrência
Máximo de 2 jobs ativos (queued + processing) por projeto. 429 com concurrency_limit_exceeded se exceder — veja Polling e retentativas.