Skip to content
Postcept
Guides

Verification results

Every verification that runs returns a result and a signed receipt. Here’s what each result means, how to tell “couldn’t check” apart from “didn’t happen,” and how to wire outcomes into recovery.

The five results

resultMeaningWhat to do
verifiedEvery postcondition passed.The action completed correctly. Store the receipt and move on.
incompleteThe action isn't complete in the system of record: missing, still pending, or in a non-final state.Check lifecycle: pending_finality means wait and re-check. Unobserved means the work didn't happen.
duplicatedThe action happened more than once.Reconcile or compensate the duplicate.
mismatchedThe action exists but a field disagrees (amount, customer, status...).Route to review, the claim doesn't match reality.
policy_failedA required policy or approval step was missing.Route per your policy before treating it as done.

Lifecycle and safe_to_claim_complete

The result tells you whether the claim held. The lifecycle tells you where the action sits in the provider’s own lifecycle. The distinction matters most for incomplete: a refund that is still pending and a refund that never happened are different situations that need different customer messages.

lifecycleMeaning
unobservedNo matching record was found in the system of record.
observedThe record exists but isn't in a transitional or terminal state.
pending_finalityThe provider reports a transitional state (a pending refund). Re-check before telling anyone.
finalizedThe provider state is terminal.
reversedA previously verified action was later undone (set by re-verification).
indeterminateThe provider returned a state Postcept doesn't recognize. Unknown never counts as final.
unreachableThe system of record couldn't be consulted.

When you only want one signal, read safe_to_claim_complete: it is true only when the outcome is verified and the provider state is terminal. When it is false, claim_reason says why, pending_finality means say “processing,” not “done” or “failed.” An unrecognized provider state derives indeterminate, never finalized, so a provider schema change can only make Postcept less confident, never more.

Postconditions: passed, failed, skipped

The receipt lists every postcondition with a status. passed and failed are self-explanatory. skipped is the important one: Postcept couldn’t make the check, an email it couldn’t resolve, a duplicate check with no correlatable id. A skipped check is never counted as a pass. It means “not confirmed,” so a result is only verified when the checks that matter actually ran and passed.

When a verification can’t run

A well-formed request returns 201 with a result. Other responses tell you to retry, fix the request, or check billing.

StatusMeaning
201Verification ran. Read result.
200Idempotent replay, the original verification for a repeated Idempotency-Key.
401Missing or invalid API key.
402Free-trial allowance reached, subscribe to keep verifying.
422Malformed request body.
429Rate limited. Back off and honor Retry-After.
503The system of record couldn't be reached, retryable.

The one to handle deliberately is 503. It means the system of record was unreachable, not that the action failed. Postcept refuses to record a false incomplete when it couldn’t actually check, so a 503 is your signal to retry with backoff, not to start recovery.

Recovery

Treat verified as done. Route incomplete, duplicated, mismatched, and policy_failed into your recovery workflow, human review, retry reconciliation, or compensation. Postcept surfaces these for you in the recovery queue, and the signed receipt is the evidence of exactly what was and wasn’t confirmed.

Idempotency

Send an Idempotency-Key header, the operation id is a natural choice. A repeat with the same key returns the original verification (200) instead of re-running, so a retry after a network blip never double-verifies or double-charges.

Idempotency-Key header
curl https://api.postcept.com/v1/verifications \
  -H "Authorization: Bearer pcpt_sk_..." \
  -H "Idempotency-Key: op_refund_8F31" \
  -H "Content-Type: application/json" \
  -d '{ "operation_id": "op_refund_8F31", "agent_id": "...", "claim": { ... } }'

Retry is not re-verification

A retry asks “did my request reach Postcept?” and the idempotency key answers it by returning the original result. Re-verification asks a different question: “has the provider state changed since?” That never rides the idempotency key. Call POST /v1/verifications/{id}/reconcile to re-check an operation against the live system of record on demand, or let the engine do it: every contract declares how long evidence in each lifecycle state stays fresh, the deadline is on the verification as next_reverify_at, and the scheduled sweep re-checks the most overdue operations first. A changed truth issues a new receipt that supersedes the old one, and the old receipt stays verifiable forever.

When the system of record is unreachable

Two different situations share the word and behave differently on purpose. On a new verification, an unreachable provider returns 503 and records no verdict: there is nothing true to sign yet, and your retry policy owns the moment. On scheduled re-verification of an existing operation, an unreachable provider leaves the last verdict standing and retries on the contract’s schedule, and if the provider stays unreachable a full day past the evidence-freshness deadline the verification derives indeterminate, because evidence past its deadline is not evidence. In both cases safe_to_claim_complete is never true on state nobody could observe.

Want the mechanics behind the checks? See postcondition verification.