Skip to main contentCertyo Developer Portal

Status & Polling

Records move through a defined lifecycle. Since Certyo doesn't yet support webhooks, clients poll to check status. This guide covers the lifecycle states and how to poll efficiently.

The record lifecycle

  1. Ingested — the record arrived at the API and was accepted (the state right after 202 Accepted)
  2. PendingAnchor — the record is in the accumulator buffer, waiting for the batch to close
  3. Batched — the accumulator flushed; a batch document was created; Merkle root computed
  4. Anchored — the batch's Merkle root was submitted to Polygon and confirmed. This is terminal for the happy path.
  5. Failed — something went wrong (blockchain timeout, IPFS pin failure, etc.). Certyo retries automatically.

Polling the snapshot status

curl "https://www.certyos.com/api/v1/snapshots?tenantId=acme-corp&recordId=order-12345" \
  -H "X-API-Key: $CERTYO_API_KEY"
Response (still pending)json
{
  "items": [
    {
      "snapshotId": "snap_xyz789...",
      "recordId": "order-12345",
      "status": "Batched",
      "onChainConfirmed": false,
      "onChainMerkleRoot": null,
      "ingestedAt": "2026-04-12T15:30:45Z",
      "updatedAt": "2026-04-12T15:31:15Z"
    }
  ]
}
Response (anchored)json
{
  "items": [
    {
      "snapshotId": "snap_xyz789...",
      "recordId": "order-12345",
      "status": "Anchored",
      "onChainConfirmed": true,
      "onChainMerkleRoot": "0x7e2d9a1b3c5f7e...",
      "ingestedAt": "2026-04-12T15:30:45Z",
      "updatedAt": "2026-04-12T15:32:30Z"
    }
  ]
}

Recommended polling pattern

Expected latency
From POST /records to status=Anchored is typically 60–90 seconds. Poll every 5–10 seconds with a max of ~15 attempts.

Exponential backoff with a cap

Node.js examplejavascript
async function waitForAnchor(recordId, tenantId, apiKey, maxWaitMs = 120000) {
  const start = Date.now();
  let delay = 5000; // start at 5s
  const maxDelay = 15000; // cap at 15s

  while (Date.now() - start < maxWaitMs) {
    const res = await fetch(
      `https://www.certyos.com/api/v1/snapshots?tenantId=${tenantId}&recordId=${recordId}`,
      { headers: { "X-API-Key": apiKey } }
    );
    const data = await res.json();
    const snapshot = data.items[0];

    if (snapshot?.status === "Anchored" && snapshot.onChainConfirmed) {
      return snapshot; // Done
    }
    if (snapshot?.status === "Failed") {
      throw new Error("Anchoring failed. Check the lifecycle endpoint for details.");
    }

    await new Promise((r) => setTimeout(r, delay));
    delay = Math.min(delay * 1.5, maxDelay); // backoff
  }

  throw new Error(`Timed out after ${maxWaitMs}ms waiting for anchor.`);
}

Bulk polling

If you're ingesting many records, don't poll one at a time. Query a time range instead:

curl "https://www.certyos.com/api/v1/snapshots?\
tenantId=acme-corp&\
fromTimestamp=2026-04-12T15:00:00Z&\
toTimestamp=2026-04-12T16:00:00Z&\
status=Anchored" \
  -H "X-API-Key: $CERTYO_API_KEY"

Lifecycle endpoint

For detailed state transition history (useful for debugging), use the lifecycle endpoint:

curl "https://www.certyos.com/api/v1/snapshots/{snapshotId}/lifecycle" \
  -H "X-API-Key: $CERTYO_API_KEY"
Responsejson
{
  "snapshotId": "snap_xyz789...",
  "currentStatus": "Anchored",
  "transitions": [
    { "status": "Ingested", "at": "2026-04-12T15:30:45Z" },
    { "status": "PendingAnchor", "at": "2026-04-12T15:30:46Z" },
    { "status": "Batched", "at": "2026-04-12T15:31:15Z", "batchId": "batch_abc..." },
    { "status": "Anchored", "at": "2026-04-12T15:32:30Z", "txHash": "0x9a8f7e..." }
  ]
}

Retrying failed anchoring

If a snapshot is stuck in Failed, Certyo's background worker will retry automatically. If you need to force an immediate retry:

curl -X POST "https://www.certyos.com/api/v1/snapshots/{snapshotId}/retry" \
  -H "X-API-Key: $CERTYO_API_KEY"
Rate limits apply
Don't retry in a tight loop. Back off for at least 30 seconds between manual retries.