Skip to documentation
AgentSEO / Docs
Browse documentation
View Markdown ↗

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

StatusMeaningClient action
queuedWork accepted and waiting to run.Keep the returned jobId.
processingAnalysis is running.Poll again after a delay.
retryingThe service is retrying work.Keep polling the same job within your deadline.
completedAnalysis finished successfully.Read result and its limitations.
failedAnalysis 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.

Bounded Node.js polling example
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.retryable and 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.

Jobs and webhooks reference · Error recovery · SDK helpers