Jobs, polling, webhooks and streaming

How long work stays safe: 202 straight away, then poll, listen for a webhook or stream progress.

Why everything is a job

Marking an essay or building a course can take minutes. Instead of holding your connection open, every operation that can take more than two seconds returns 202 and a job at once. Your request never times out and a dropped connection loses nothing.

  1. Queued202 and a job id, straight away
  2. ProcessingPoll, stream or wait for a webhook
  3. SucceededResult and files on the job
Every AI request runs as a background job, so nothing times out.
StatusMeaningCharged
queuedAccepted and waiting for a worker.Reserved
processingRunning. progress shows the stage.Reserved
succeededFinished. result and files are ready.Yes
needs_contextFinished, but something needed was missing. result.reason says what.No
failedCould not finish. error explains why.No
cancelledYou cancelled it.No
expiredThe result was deleted after the retention period.—

Polling (the main path)

Call GET /v1/jobs/{id}. Wait at least poll_after_seconds (or the Retry-After header) between calls; the value grows for long jobs. Polling is free.

async function waitForJob(id) {
  for (;;) {
    const response = await fetch(`https://api.edumark.ai/v1/jobs/${id}`, { headers: { Authorization: `Bearer ${process.env.EDUMARK_API_KEY}` } });
    const job = await response.json();
    if (!['queued', 'processing'].includes(job.status)) return job;
    const wait = Number(response.headers.get('Retry-After')) || job.poll_after_seconds;
    await new Promise((resolve) => setTimeout(resolve, wait * 1000));
  }
}

Webhooks

Rather than polling, add an HTTPS endpoint in the console and EduMark tells you the moment a job finishes with a signed job.completed notice. See webhooks for the payload, signatures and retries.

Streaming progress

For a live progress bar, open GET /v1/jobs/{id}/events. It is a server-sent event stream with progress, status and done events and a heartbeat every 15 seconds. Each stream closes after 25 seconds; reconnect with the Last-Event-ID header and you carry on where you left off. Keep polling as your source of truth.

Node.js
const response = await fetch(`https://api.edumark.ai/v1/jobs/${id}/events`, {
  headers: { Authorization: `Bearer ${process.env.EDUMARK_API_KEY}`, Accept: 'text/event-stream' },
});
const decoder = new TextDecoder();
for await (const chunk of response.body) {
  for (const line of decoder.decode(chunk, { stream: true }).split('\n')) {
    if (line.startsWith('data: ')) render(JSON.parse(line.slice(6)));
  }
}
// The stream closes after 25 s: reconnect with Last-Event-ID, or fall back to polling.

Cancel and delete

POST /v1/jobs/{id}/cancel stops a queued or running job and releases what was reserved. DELETE /v1/jobs/{id} deletes the request, result and files straight away; otherwise they are deleted after 30 days.

NextWebhooks