TrustWixDocs
Handle results

Check existing applicants

Reconciling a list of your own users against the verifications you already hold

You have a list of email addresses from your own database and you want to know which of those people have verified with you. One call answers up to a thousand of them.

curl https://api.trustwix.com/v1/verifications/lookup \
  -H "Authorization: Bearer TWK_..." \
  -H "Content-Type: application/json" \
  -d '{
    "emails": ["ana@example.com", "bo@example.com", "cai@example.com"]
  }'
Response
{
  "object": "list",
  "mode": "live",
  "data": [
    {
      "object": "verification_lookup",
      "email": "ana@example.com",
      "found": true,
      "attempts": 2,
      "ever_approved": true,
      "first_attempt_at": "2026-03-01T10:00:00.000Z",
      "latest": {
        "id": "vs_2f8a1c9b4e7d",
        "status": "rejected",
        "verdict": "reject",
        "score": 0.41,
        "reason_codes": ["FACE_MISMATCH"],
        "flow": "flw_id_and_liveness",
        "mode": "live",
        "created_at": "2026-07-01T10:00:00.000Z",
        "updated_at": "2026-07-01T10:04:12.000Z",
        "expires_at": null
      }
    },
    {
      "object": "verification_lookup",
      "email": "bo@example.com",
      "found": false,
      "attempts": 0,
      "ever_approved": false,
      "first_attempt_at": null,
      "latest": null
    }
  ]
}

You get exactly one result per entry, in the order you sent them, echoed back verbatim. Nothing is collapsed or reordered, so data[i] always answers emails[i] and you can map the response onto your own list by index without normalising anything first. Send the same address twice and you get two identical rows.

Matching ignores case and surrounding whitespace, so Ana@Example.COM and ana@example.com are the same person and will return the same verification.

Read ever_approved, not just latest.status

latest is the most recent attempt. ever_approved looks across every attempt this address has.

Ana above is the case that catches people out. She passed in March, tried again in July after changing her name, and that second attempt was rejected. Her latest.status is rejected and her ever_approved is true. Whether she should keep her access depends on your own policy, but you have to see both numbers to make that decision, and a reconciliation script that reads only latest.status will lock out every customer who ever re-verified and failed.

attempts gives you the rest of the picture. A high count against ever_approved: false is somebody struggling with the flow, and it is worth a look at whether the flow itself is the problem.

What found: false actually tells you

It tells you we hold no verification carrying that address in this environment, right now. It does not tell you the person never verified. Three quite different situations produce the same answer.

They never started one. The straightforward case, and usually the one you are looking for.

We were never told their address. This is the one to check first if a reconciliation run comes back almost entirely empty. An address reaches a verification in exactly two ways: you send applicant.email when you create it, or the applicant completes an email step in the hosted flow. If you do neither, we hold sessions that ran perfectly and completed normally, with no address on them, and every lookup you run will miss all of them.

Sending the address at create time
curl https://api.trustwix.com/v1/verifications \
  -H "Authorization: Bearer TWK_..." \
  -H "Content-Type: application/json" \
  -d '{
    "flow": "flw_id_and_liveness",
    "applicant": { "reference_id": "your-user-123", "email": "ana@example.com" }
  }'

Their retention window has elapsed. When a verification passes out of retention we destroy the stored identity and the search fingerprint in the same statement. A record we have promised to forget cannot still answer "yes, that person verified with us", so it stops being findable at the same moment it stops existing. This is the retention promise working rather than a gap to route around.

Do not treat `found: false` as permission to delete

If your own records say someone verified and a lookup says found: false, the most likely explanations are the second and third above, not that your records are wrong. Reconcile in the direction of adding what you are missing, never of deleting what you already hold.

Sandbox and live are separate

A lookup answers within the environment of the key that made it. A live key never sees sandbox sessions, and the reverse. The mode on the envelope tells you which environment answered, so every found: false in a response means "not in this one".

If you tested with real colleagues' addresses in sandbox and then look them up with a live key, they come back as not found. That is correct.

Checking one address

The single-address form is a filter on the list endpoint, and it returns the full verification resource rather than a summary.

curl "https://api.trustwix.com/v1/verifications?email=ana%40example.com" \
  -H "Authorization: Bearer TWK_..."

Same exact matching, newest first, and the usual cursor pagination if that address has more verifications than fit on a page. Prefer the batch endpoint for more than a handful: it keeps the addresses out of the URL and costs you far less of your rate limit.

A request body is not automatically private

Keeping addresses out of the URL keeps them out of proxy access logs, browser history and Referer headers. It does not keep them out of anything that logs request bodies. Redact these payloads in your own application logs, tracing and error reporting.

Limits, and what a run of 3,000 looks like

One call carries up to 1,000 addresses. Larger lists are more than one call.

The rate limit charges by size rather than by call, at one token per 50 addresses out of your budget of 100 per minute. A full 1,000-address call costs 20 tokens, so a 3,000-address reconciliation is three calls costing 60 and clears comfortably inside a single minute.

Permissions

verification:read, which is on every key by default. The response carries no applicant identity: no name, no date of birth, no document number, no images. It answers only the question you already knew the answer to, since you supplied the address. To read who somebody is, use applicant data on a single verification, which needs the applicant:read scope.

When the answer is unavailable

If a lookup cannot be answered you get a 503 with the code LOOKUP_UNAVAILABLE, never an empty result. The distinction is deliberate and it matters: an empty result and an unanswered question look identical to a script, and quietly treating one as the other means re-onboarding thousands of customers who were already approved. Retry with backoff, and treat the addresses in that call as unknown rather than as unverified.

On this page