Operate reliably
Wait for evidence.
Handle every outcome.
Most analysis POST endpoints return a queued job. Submission and completion are separate events: preserve the job ID, poll with a deadline, and branch on the terminal state.
The job lifecycle
| Status | Meaning | Client action |
|---|---|---|
| queued | Work accepted and waiting to run. | Keep the returned jobId. |
| processing | Analysis is running. | Poll again after a delay. |
| retrying | The service is retrying work. | Keep polling the same job within your deadline. |
| completed | Analysis finished successfully. | Read result and its limitations. |
| failed | Analysis ended with an error. | Stop; inspect the error and choose recovery. |
A status response can also include phase, progress, attempt, and max_attempts. Progress is informational; only a terminal status should release the next workflow step.
Poll with a deadline
This Node.js example polls an existing job; it does not create another billable request. Set AGENTSEO_API_KEY and AGENTSEO_JOB_ID first. The 90-second deadline is an application choice, not a promised completion time.
const apiKey = process.env.AGENTSEO_API_KEY;
const jobId = process.env.AGENTSEO_JOB_ID;
if (!apiKey || !jobId) throw new Error("Set AGENTSEO_API_KEY and AGENTSEO_JOB_ID");
const deadline = Date.now() + 90_000;
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
let completed = false;
while (Date.now() < deadline) {
const response = await fetch(
"https://www.agentseo.dev/api/v1/jobs/" + encodeURIComponent(jobId),
{ headers: { "x-api-key": apiKey },
signal: AbortSignal.timeout(Math.min(10_000, deadline - Date.now())) }
);
if (!response.ok) throw new Error("Polling HTTP " + response.status);
const job = await response.json();
if (job.status === "failed") {
throw new Error(job.error?.code + ": " + job.error?.message);
}
if (job.status === "completed") {
console.log(JSON.stringify(job.result, null, 2));
completed = true;
break;
}
await sleep(Math.min(2_000, Math.max(0, deadline - Date.now())));
}
if (!completed) throw new Error("Client deadline reached; save the job ID to resume later");For submission, prefer the returned retry_after_seconds when present. On a polling HTTP error, this example stops and reports it. Add deliberate rate-limit and transient-error recovery for your application; do not retry every error indefinitely.
Retry the right operation
- Still running: poll the existing job. Resubmitting is different from checking status.
- Client deadline reached: retain the ID and resume status checks later. A client timeout does not cancel server work.
- Terminal failure: inspect
error.retryableand the error code. A new submission may consume credits. - Submission timeout: for endpoints that support
Idempotency-Key, reuse the same key for the same logical request. Use a new key for different work; do not reuse it with changed inputs. - 400, 401, 402, 403: fix the input, key, credits, or policy before repeating a request.
Preserve request IDs for support. Keep API keys and private page content out of logs.
Inline waits, events, and exceptions
?sync=true on supported endpoints opens a short processing window. It can still return 202, so keep your job handling even in synchronous-looking code.
Job events provide an authenticated server-sent event stream. Browser-native EventSource cannot attach an arbitrary API-key header; use a server-side client or an authenticated fetch-stream implementation.
/extract and webhook management are synchronous. /audit/local/batch returns multiple job IDs. Check each endpoint’s contract instead of assuming every POST uses the same response shape.