Preview: these APIs are still being designed and aren't available yet.
Skip to Content
UpskyDocs

Overview

Confirm who is behind every account.

Use Upsky Verify to check a person's identity before they use your app. It has verification tiers, from a selfie with an age estimate to an official ID matched to the person's face.

How it works

  1. Your server creates a session.
  2. You send the person to the session's url, a page on verify.upsky.dev that works on any phone. You can also start one from the dashboard, which gives you a QR code and a link to share.
  3. They agree to the notice, photograph their ID if the tier needs it, and take a selfie.
  4. Upsky runs the checks and sends a webhook with the result. You can also read the session at any time.

You're charged only when a session reaches a decision. A link that expires or that you cancel costs nothing.

Create a session

curl https://api.upsky.dev/v1/verify/sessions \
  -H "Authorization: Bearer $UPSKY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "tier": "identity", "reference": "user_123", "return_url": "https://example.com/done" }'
FieldDefaultWhat it does
tieridentityage or identity
referenceYour own ID for the person
return_urlWhere the person goes after submitting
minimum_age18From 13 to 25
allow_selfie_uploadfalseLet the person send a selfie from their photos
show_resultfalseShow the person the result on the link
expires_in86400Seconds the link works, from an hour to seven days
project_idOne of your projects, to group sessions
metadataUp to 20 keys of your own
optionsAccepted IDs, outcome rules and photo retention
expectedWhat you expect the ID to say

Selfies

By default the selfie has to be taken with the camera on the page, there and then. Set allow_selfie_upload to true to also let the person choose a photo they already have. The session returns the setting as allow_selfie_upload, and the dashboard has the same switch when you start a verification.

Live sessions always take the selfie with the camera, through the liveness check, whatever this is set to.

Showing the result

By default the link only thanks the person, and you tell them the result yourself. Set show_result to true to show it on the link instead: right after they send their photos, the page waits for the checks and says whether they were verified, weren't, or are being reviewed. It never says why, so the page can't be used to learn what gets past the checks. The dashboard has the same switch when you start a verification.

If a session in review is later approved or rejected, the link shows the new result the next time it's opened.

Approval thresholds

How sure each check must be is your team's choice, in the dashboard under Verify → Settings. Pick a preset, or set each number yourself with Custom:

PresetLiveness passLiveness reviewFace match passFace match reviewAge margin
Relaxed654090802 years
Balanced805095853 years
Strict907098925 years

A score at or above a pass mark passes. From the review mark up to the pass mark, the session goes to review (liveness_inconclusive or face_match_inconclusive), so a person can look. Below the review mark it's rejected (liveness_failed or face_mismatch). On the age tier, a selfie alone passes when the person looks at least minimum_age plus the age margin: the middle of the estimated range, returned as checks.age.estimated_age. For an 18+ check, that's from looking 20 on Relaxed, 21 on Balanced and 23 on Strict. A range entirely under minimum_age is rejected, and anything between goes to review.

Balanced is the default. A project can have its own thresholds, chosen in the same place: a session with that project_id uses them, and every other session uses your team's. Each session keeps the thresholds it was created with, returned as thresholds, so changing your settings never changes how an existing session is decided. When a liveness check scores under the pass mark, the page suggests trying again, within the link's three tries.

Accepted IDs

On the identity tier, you choose which IDs a person may use and which countries they may come from, in the dashboard under Verify → Settings or per session:

curl https://api.upsky.dev/v1/verify/sessions \
  -H "Authorization: Bearer $UPSKY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "options": {
      "documents": {
        "allowed_types": ["passport", "mx_ine"],
        "allowed_countries": ["MEX", "USA"]
      }
    }
  }'

allowed_types is any of mx_ine, passport, us_drivers_license and id_card. The person only sees the ones you accept, and isn't asked to choose when there's just one. allowed_countries takes ISO 3166-1 alpha-3 codes; leave it empty to accept any country. An INE only counts as MEX and a US driver's license as USA, so a request that accepts neither a matching country nor another ID is refused.

The ID is read as soon as its photos are in. If it's blurry, isn't the kind the person chose, has expired or comes from a country you don't accept, the page says so and asks for new photos, up to max_attempts times (3 by default, 1 to 5). After the last try the person sends the photos they have, and your outcome rules decide.

Outcome rules

Each problem a check can find leads to one of three outcomes: reject the session, send it to review, or ignore it. Set them in Verify → Settings, or per session in options.rules:

RuleDefaultCan be
liveness_inconclusivereviewreject, review
age_inconclusivereviewreject, review
document_unreadablerejectreject, review
document_expiredrejectreject, review, ignore
document_checksum_failedrejectreject, review
document_type_mismatchreviewreject, review
document_country_not_acceptedrejectreject, review
date_of_birth_missingreviewreject, review
face_match_inconclusivereviewreject, review
age_estimate_mismatchreviewreject, review, ignore
name_mismatchreviewreject, review, ignore
date_of_birth_mismatchreviewreject, review, ignore

A failed liveness check, a selfie with no face, a face that isn't the ID's and an age under the minimum always reject.

Expected data

If you already know who the person should be, send it as expected and the ID is compared with it:

{
  "tier": "identity",
  "expected": {
    "first_name": "Ana",
    "last_name": "García",
    "date_of_birth": "1992-04-15"
  }
}

Names match whatever their accents, case or middle names: "Ana" matches "ANA MARIA". The result is in checks.data_match, field by field: match, mismatch, or not_read when the ID gave nothing to compare. A difference sends the session to review unless you change the name_mismatch or date_of_birth_mismatch rule.

Branding

The page a link opens can carry your name, your logo, your color and a short message of your own. Set them in Verify → Settings, for your team or for a project; a session with that project_id uses the project's. Changes reach links you've already sent.

Data retention and redaction

Photos are deleted 30 days after a session ends. Change that in Verify → Settings, or per session with options.retention_days (1 to 365). A session waiting for your review keeps its photos at least 7 days.

To delete one person's data sooner, redact the session:

curl -X POST https://api.upsky.dev/v1/verify/sessions/$SESSION_ID/redact \
  -H "Authorization: Bearer $UPSKY_API_KEY"

This deletes the photos, what was read from the ID, the expected data, and your reference, metadata and return_url, right away. The status, reasons and checks stay as your record, and redacted_at says when it happened. It can't be undone, only works on a session that has ended, and sends a verify.session.redacted webhook.

Every setting above follows the same order: what the request sets, then the project's settings, then your team's. Each session keeps the options it was created with, returned as options.

Session status

StatusMeaning
pendingThe link hasn't been opened
in_progressThe person is taking their photos
processingSubmitted; the checks are running
approvedEvery check passed
rejectedA check failed. reasons says which
needs_reviewThe checks couldn't decide. Approve or reject it in the dashboard
expiredThe link wasn't finished in time. Not charged
canceledYou canceled it. Not charged

A decided session has checks (liveness, age, document, face match and, when you sent expected, data match, with their scores) and, for the identity tier, verified_outputs: the name, date of birth and document details read from the ID.

Test mode

Sessions made with a test key run the whole flow with simulated checks and are never charged. On the last step, the person picks the result they want, so you can test approved, rejected and needs-review without real IDs.

Live mode

Sessions made with a live key run real checks and are charged when they reach a decision. Live sessions are open for the age and identity tiers.

The person gets three tries at the liveness check on each link. After the third, the link can't continue: create a new session for them. A link that runs out of tries and expires isn't charged.

On this page