POST /media/download você recebe job_id e deve consultar GET /jobs/{jobId} até conclusão.
Intervalo recomendado
| Fase | Intervalo |
|---|---|
| Primeiros 30 s | 2–3 s |
| Após 30 s | 5 s |
| Após 2 min | 10 s |
Polling com backoff
async function pollJob(jobId) {
let delay = 2000;
for (let i = 0; i < 60; i++) {
const job = await getJob(jobId);
if (job.status === 'completed') return job;
if (job.status === 'failed') throw new Error(job.error?.code);
await sleep(delay);
delay = Math.min(delay * 1.5, 10000);
}
throw new Error('timeout');
}
Retentativa em 429
def request_with_retry(fn, max_attempts=5):
for attempt in range(max_attempts):
try:
return fn()
except requests.HTTPError as exc:
if exc.response.status_code != 429:
raise
reset = int(exc.response.headers.get("X-RateLimit-Reset", 0))
time.sleep(max(reset - time.time(), 2 ** attempt))
raise RuntimeError("rate limit exceeded")
Idempotência
- Guarde
job_idno seu banco antes de fazer polling - Não enfileire o mesmo download duas vezes para a mesma URL sem checar jobs ativos
- Use
request_iddo headerX-Request-Idnos logs
Quando parar
| Situação | Ação |
|---|---|
completed | Sucesso — baixar arquivo |
failed + media_unavailable | Não retentar — URL inválida |
failed + internal_error | Retentar com backoff |
404 no job | Expirou — novo download |
429 | Aguardar reset |
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();
}