Working with async checks

Submit, poll at a sane interval, handle every final status, cancel when the caller gives up, and never leave credits reserved.

A check is a job. POST /v1/checks accepts it and answers with an id in well under a second; the work runs on Silvertext's side and you read the result back by id. This guide is the lifecycle in full.

Statuses

StatusPhaseWhat it means
queuedWaitingAccepted and reserved; a worker will pick it up.
extractingRunningReading the file, OCR for scanned PDFs and images.
analyzingRunningLanguage detection, grammar, style and AI passes.
searchingRunningWeb search and source fetching for plagiarism matches.
scoringRunningVerdicts, scores and the report assembled.
doneFinalThe report is ready; credits are settled.
rejectedFinalThe document could not be checked after acceptance (rejection.code says why). No charge.
failedFinalSomething broke on our side. No charge; safe to resubmit.
cancelledFinalYou cancelled it. No charge.

While a check runs, progress climbs from 0 to 1 and phase is a short label you can show a user.

Polling

Poll GET /v1/checks/{id}?view=summary. The summary view is the scalars and scores, a few hundred bytes, so it costs nothing to poll and counts one request against the per-minute limit.

  • Start at 2 to 3 seconds between polls. Most checks finish in about a minute; a 25,000-word document with many sources can take several.
  • Back off gently on long checks: 3 seconds for the first minute, 5 after that, capped at 10.
  • Treat done, rejected, failed and cancelled as final. Anything else is still running.
  • Give up on your side after a deadline you choose (ten minutes is generous), and cancel the check when you do, so its credits are released.
poll.ts
const FINAL = new Set(['done', 'rejected', 'failed', 'cancelled']);

export async function waitForCheck(id: string, { deadlineMs = 10 * 60_000 } = {}) {
  const started = Date.now();
  let delay = 3000;
  for (;;) {
    const check = await api(`/v1/checks/${id}?view=summary`);
    if (FINAL.has(check.status)) return check;
    if (Date.now() - started > deadlineMs) {
      await api(`/v1/checks/${id}/cancel`, { method: 'POST' });
      throw new Error(`check ${id} did not finish in time`);
    }
    await new Promise((r) => setTimeout(r, delay));
    if (Date.now() - started > 60_000) delay = Math.min(delay + 2000, 10_000);
  }
}

Cancelling

Stops a queued or running check and releases its credit reservation. The API answers 202 immediately and the status becomes cancelled shortly after; a check that already reached a final status answers 409 already_final.

Deleting a report

Removes a finished report and its document from the account. A running check answers 409 still_running; cancel it first. Delete reports your product no longer needs, for example after you have copied the findings into your own store.

Concurrency

Five checks may be in flight per account at once. A sixth submission answers 409 concurrent_limit. A batch integration keeps a small pool of in-flight ids, polls them round-robin, and submits the next document as each one finishes; Batch checking has the pattern. Do not retry a 409 in a tight loop; wait for a slot.

On this page