# API Reference

# Introduction

Sum&Substance's API allows you to send and receive applicants’ data and documents for verification through simple RESTful APIs. API responds with JSON payload (unless otherwise stated). When data payload should be provided, it expects parameters to be passed in valid JSON (except when stated otherwise) with the Content-Type: application/json header being set.

All incompatible changes made to API will be versioned and won't affect existing endpoints. However, we may add additional fields to JSON response without any notice or API versioning. So it's not a good idea to rely on object mappers in your integration code that will fail on unknown properties.

Note that header names are not case-sensitive and may vary depending on your ways of implementation at both your requests and our responses.

We have two environments:

  • Sandbox: this is only needed when testing out your integration with Sumsub. No real checks are performed when in the sandbox environment.
  • Production: In order to provide you with access to the production environment, we require that the integration is tested first. We just want to make sure that everything works the way it should.

Both environments are available via api.sumsub.com and are controlled by the switch in the top right-hand corner of the dashboard. API accesses are regulated through the use of the X-App-Token authorization header value.

# Postman

You can download, import and run the Sumsub API Postman collection:

Download ⤓

In your Postman environment, you'll need to define the variables app-token and secret-key.

You can read more in Postman’s documentation about managing environments..

# Authentication

All API queries must be made over HTTPS, and plain HTTP will be refused. You must include your X-App headers in all requests.

# App Tokens

This is the most secure method of talking to our API programmatically.

You can generate App Tokens in the dev space on our dashboard.

Full sized App Token and Secret Key values are shown only once at the dashboard in the moment of App Token creation, and their settings cannot be changed later.

# Making a request

All requests must contain the following headers:

  • X-App-Token - an App Token that you generate in our dashboard
  • X-App-Access-Sig - signature of the request in the hex format and lowercase (see below)
  • X-App-Access-Ts - number of seconds since Unix Epoch in UTC

# Signing a request

The value of the X-App-Access-Sig is generated by a sha256 HMAC algorithm using a secret key (provided upon App Token generation) on the bytes obtained by concatenating the following information:

  • A timestamp (value of the X-App-Access-Ts header) taken as a string
  • An HTTP method name in upper-case, e.g. GET or POST
  • URI of the request without a host name, starting with a slash and including all query parameters, e.g. /resources/applicants/123?fields=info
  • Request body, taken exactly as it will be sent. If there is no request body, e.g., for GET requests, don't include it.

Example of the string to be signed to get an accessToken: 1607551635POST/resources/accessTokens?userId=cfd20712-24a2-4c7d-9ab0-146f3c142335&levelName=basic-kyc-level&ttlInSecs=600

Your timestamp must be within 1 minute of the API server time. Make sure the time on your servers is correct.

# Examples

Here are some examples how you can sign requests

Language App Token usage example
JS Example
Java Example
PHP [Guzzle] Example
Python Example
Ruby Example
GO Example
C# Example

# Access tokens for SDKs

To initialize SDK for Sandbox environment (to not create a real verification case on production) make sure to use App token and Secret key pair that was created on Sandbox for request authorization headers.

When initializing WebSDK or MobileSDK, an access token authentication must be used.

POST /resources/accessTokens?userId={userId}&levelName={levelName}

# REQUEST ARGUMENTS
Name Type Required Description
ttlInSecs Integer No Lifespan of a token in seconds. Default value is equal to 10 minutes.
userId String Yes An external user ID which will be bound to the token. It correlates to externalUserId of an applicant.
levelName String Yes A name of the level configured in the dashboard.
externalActionId String No An external action ID which will be bound to the token. More info about applicant actions you can find here.

If your userId or levelName contains reserved characters (e.g., "@", "+", white spaces as %20) it should be URL encoded otherwise you may get signature mismatch or just an invalid parameter value.

# RESPONSE
Name Type Description
token String A newly generated access token for an applicant.

An access token for applicant has limited access to the API, e.g., it’s only valid for 1 applicant and can't access other applicants.

# Creating an access token for applicant
# Example response

Make sure your integration code does not validate or analyze the access token content, as the format is not fixed and may undergo further changes in the future. The token must be treated as an arbitrary string with the maximum length of 1KB.

You can also use a request body to provide us with parameters to create an accessToken.

POST /resources/accessTokens

# REQUEST BODY
Name Type Required Description
ttlInSecs Integer No Lifespan of a token in seconds. Default value is equal to 10 minutes.
userId String Yes An external user ID which will be bound to the token. It correlates to externalUserId of an applicant.
levelName String Yes A name of the level configured in the dashboard.
externalActionId String No An external action ID which will be bound to the token. More info about applicant actions you can find here.
# Example request
# Example response

# API Health

You can check operational status of our service using request below. HTTP status 200 in response confirms availability of our API.

Note that the request should be authorized with headers mentioned above.

GET /resources/status/api

# Example request

You may also check our uptime history and current service status at the Sumsub Status page.

# Errors

On API errors we return standard HTTP Status Codes.

Please note that we have a limit for the following API requests that you may send. If there are too many requests from you, we will return status code 429. Please notify our team if you have to make a vast amount of requests during a particular time frame.

Response body contains a JSON payload with additional information. For example:

# Response body
Name Type Optional Description
description String No Human-readable error description.
code Integer No HTTP Status code.
correlationId String No This id uniquely identifies the error. You can send this id to us, in case the cause of the problem is still unclear.
errorCode Integer Yes Code of the exact problem (see the Error codes section). For some errors may not be present.
errorName String Yes String representation of the exact problem (see the Error codes section). It always appears when errorCode presents.

# Error codes

Please, note that not all error responses will contain error code and name. More and more errors will get their codes in the future.

Code Name Description
1000 duplicate-document Duplicate document (image, video) was uploaded. Exact equality is taken into account.
1001 too-many-documents Applicant contains too many documents. Adding new ones is not allowed.
1002 file-too-big Uploaded file is too big (more than 64MB).
1003 empty-file Uploaded file is empty (0 bytes).
1004 corrupted-file File is corrupted or incorrect format (e.g. PDF file is uploaded as JPEG).
1005 unsupported-file-format Unsupported file format (e.g. a TIFF image).
1006 no-upload-verification-in-progress Applicant is being checked. Adding new data is not allowed.
1007 incorrect-file-size The file size should be more or less than custom values from global settings at the dashboard.
1008 applicant-marked-as-deleted Applicant is marked as deleted/inactive. No action allowed to change its status.
1009 applicant-with-final-reject Applicant is rejected with rejection type of FINAL. No adding new data/files allowed.
1010 doc-type-not-in-req-docs Attempt to upload a document outside of the applicant level set/set of required documents.
3000 applicant-is-already-in-the-state An attempt to change the status of the applicant against the logic - applicant is already in required state.
4000 app-token-invalid-format Invalid format of X-App-Token value.
4001 app-token-not-found App token doesn't exist (e.g. test env. token used on production).
4002 app-token-private-part-mismatch Private part of the token (after dot) doesn't match public part.
4003 app-token-signature mismatch Signature encoded value doesn't match request content.
4004 app-token-request-expired X-App-Access-Ts doesn't match to the number of seconds since Unix Epoch in UTC.
4005 app-token-invalid-value Invalid authentication header values were provided.
4006 app-token-not-all-auth-params-provided Not all required authorization headers were provided.
4007 app-token-invalid-params Invalid authentication parameters were provided.
5000 applicant-already-blacklisted Attempt to blocklist an applicant that is already blocklisted.
5001 applicant-already-whitelisted Attempt to whitelist an applicant that is already whitelisted.

If you still don't understand what the issue is, please, contact us with the correlationId provided.

# Applicants API

# Creating an applicant

An applicant is an entity representing one physical person. It may have several ID documents attached, like an ID card or a passport. Many additional photos of different documents can be attached to the same applicant.

POST /resources/applicants?levelName={levelName}

When creating an applicant, you don't have to send us prefilled applicant data such as names, dob, addresses, etc. You only need to send us images and get the recognized document data by this method. It's the best way to increase your conversion because rejects of mismatches in names and typos will be excluded.

If you have requirements to make data cross-validation and have no possibility to perform it on your side you can use fixedInfo object to fill data like names, date of birth and address.

# REQUEST ARGUMENTS
Name Type Required Description
levelName String Yes Name of user verification steps configuration that you should set up at the dashboard.
#{body} Object Yes An object representing an applicant (see example).

Please note that the levelName is case-sensitive and has to be created at the same environment.

# REQUEST BODY
Name Type Required Description
externalUserId String Yes An applicant ID on the client side, should be unique.
sourceKey String No If you want to separate your clients that send applicants, provide this field to distinguish between them. It also shows up at the webhook payloads.
email String No Applicant email.
phone String No Applicant phone number.
lang String No The language in which the applicant should see the result of verification in ISO 639-1 format.
metadata Array of objects No Additional information that is not displayed to the end user ( Example: [{"key": "keyFromClient", "value": "valueFromClient"}] ).
fixedInfo Object No Basic information about the applicant that we shouldn't change from our side but cross-validate it with data recognized from documents. Has the same attributes as info.
# fixedInfo and info ATTRIBUTES
Name Type Required Description
firstName String No First name.
lastName String No Last name.
middleName String No Middle name.
firstNameEn String No Automatic transliteration of the first name.
lastNameEn String No Automatic transliteration of the last name.
middleNameEn String No Automatic transliteration of the middle name.
legalName String No Legal name.
gender String No Sex of a person (M or F).
dob String No Date of birth (format YYYY-mm-dd, e.g. 2001-09-25).
placeOfBirth String No Place of birth.
countryOfBirth String No Country of birth.
stateOfBirth String No State of birth.
country String No Alpha-3 country code (e.g. DEU or GBR) (Wikipedia).
nationality String No Alpha-3 country code (Wikipedia).
addresses Array No List of addresses.
tin String No Tax Identification Number.
# addresses ELEMENTS FIELDS
Name Type Required Description
country String No Alpha-3 country code.
postCode String No Postal code.
town String No Town or city name.
street String No Street name.
subStreet String No Additional street information.
state String No State name if applicable.
buildingName String No Building name if applicable.
flatNumber String No Flat or apartment number.
buildingNumber String No Building number.

An applicant entity with the associated ID (refer to the example). Persist the id field after creating the applicant - it will be needed for further requests that you intend to make.

For the IDENTITY step we recommend the following document types: PASSPORT, ID_CARD, RESIDENCE_PERMIT, DRIVERS

If levelName contains reserved characters (e.g., "@", "+", white spaces as %20) it should be URL encoded otherwise you may get signature mismatch. The same applies to all endpoints below.

# Example request
# Example response

# Changing required document set (level)

To update the list of required documents based on the level provided, you can use the following method. For example, if you initially required only an ID document and selfie for verification, and now need to add a proof of address, you can simply add this additional step to the list of required documents.

If you're using our SDKs you can just initialize it with suitable levelName on accessToken and SDK will change level automatically.

POST /resources/applicants/{applicantId}/moveToLevel?name={levelName}

Make sure to check the applicant status before changing the level. You won't be able to change level if reviewStatus of the applicant is pending, queued or onHold.

# REQUEST ARGUMENTS
Name Type Required Description
applicantId String Yes Applicant ID
levelName String Yes Name of new user verification steps configuration from the dashboard.
# RESPONSE

A new requiredIdDocs and levelName will be returned.

# Example request with body for changing required document set
# Example response

If you'd like to get current requiredIdDocs you may call POST /resources/applicants/{applicantId}/requiredIdDocs

# Example request body for getting current required document set

# Adding an ID document

This method gets a multipart form: document JSON metadata and, optionally, a document photo. If a document with the same metadata type and country already exists, the new data will be merged with the existing data. Any existing data will be overwritten if it is also present in the new object. However, a new image will always be added. If you don't yet know the document metadata, please include the type and country fields, which are mandatory. For example, you might send "PASSPORT" and "GBR".

POST /resources/applicants/{applicantId}/info/idDoc

# REQUEST HEADERS
Name Type Value
Content-Type String multipart/form-data
X-Return-Doc-Warnings Boolean true/false

For adding a Proof of Address or Identity type of documents it's advised to use header X-Return-Doc-Warnings with value of true which allows determining if document data is readable and acceptable before moving the applicant to pending status: in response errors or warnings array will present. If you happen to encounter an error during the upload process, please note that the image you uploaded will be marked as inactive. This means that it won't be used as the primary document for the verification step. You will need to upload another image in order to proceed with the verification process successfully.

For double-sided documents (FRONT_SIDE and BACK_SIDE of one idDocType value) both images will be marked inactive even if you've got an error for only one of them. Array of warnings just makes a notice that there might be something wrong with a document.

Warnings and errors are shown only for the first four attempts to upload a document. For next cases we assume that document should be checked within the full verification process.

We may change country and idDocType values that were sent to us on image upload, so if that sort of mapping is important to your side, make sure to compare these object from our response.

Please note that it's a simple and fast check and may not send errors for some document types to provide better conversion.

Note that applicant won't able to be moved to pending status for a check if one of verification steps is not active (doesn't have active images within itself). To check if a verification step is active you can call this method.

# REQUEST ARGUMENTS
Name Type Required Description
applicantId String Yes Applicant ID
# FORM DATA
Name Type Required Description
metadata Object Yes An object representing an ID document
content Binary No A photo of a document
# REQUEST metadata BODY PART FIELDS
Name Type Required Description
idDocType String Yes See supported document types here
idDocSubType String No FRONT_SIDE, BACK_SIDE or null
country String Yes 3-letter country code (Wikipedia)
firstName String No First name
middleName String No Middle name
lastName String No Last name
issuedDate String No Issued date (format YYYY-mm-dd, e.g. 2001-09-25)
validUntil String No Valid until date (format YYYY-mm-dd, e.g. 2001-09-26)
number String No Document number
dob String No Date of birth
placeOfBirth String No Place of birth
  • If a document is double-sided submit two images and set up idDocSubType properly (FRONT_SIDE and BACK_SIDE)
  • Make sure to send BACK_SIDE if FRONT_SIDE was already sent otherwise verification step won't be completed, and you won't be able to initiate a check.
# RESPONSE

JSON representing added document information.

If you need to know the imageId of the photo, you can find this information in the response header X-Image-Id

# Available values of errors array
Name Description
forbiddenDocument Unsupported or unacceptable type/country of document.
differentDocTypeOrCountry Document type or country mismatches ones that was sent with metadata and the recognized type is forbidden by settings.
missingImportantInfo Not all required document data can be recognized.
dataNotReadable There is no available data to recognize from image.
expiredDoc Document validity date is expired.
documentWayTooMuchOutside Some parts of the document are cropped.
noIdDocFacePhoto Face is not clearly visible on the document.
selfieFaceBadQuality Face is not clearly visible on the selfie.
screenRecapture Image might be a photo of screen.
screenshot Image is a screenshot.
sameSides Image of the same side of document was uploaded as front and back sides.
shouldBeMrzDocument Sent document type should have an MRZ, but there is no readable MRZ on the image.
shouldBeDoubleSided Two sides of the sent document should be presented.
shouldBeDoublePaged The full double-page of the document (usually, two main passport pages) are required.
documentDeclinedBefore The same image was uploaded and declined earlier in the same applicant.
# Available values of warnings array
Name Description
badSelfie Make sure that your face and the photo in the document are clearly visible.
dataReadability Please make sure that the information in the document is easy to read.
inconsistentDocument Please ensure that all uploaded photos are of the same document.
maybeExpiredDoc Your document appears to be expired.
documentTooMuchOutside Please ensure that the document completely fits the photo.
# Example request
# Example response

In cases when only the data of the document needs to upload:

# Example response

# Supported document types

Value Description
ID_CARD An ID card
PASSPORT A passport
DRIVERS A driving license
RESIDENCE_PERMIT Residence permit or registration document in the foreign city/country
UTILITY_BILL Proof of address document. Check here for the full list of acceptable docs as UTILITY_BILL
SELFIE A selfie with a document
VIDEO_SELFIE A selfie video (can be used in webSDK or mobileSDK)
PROFILE_IMAGE A profile image, i.e. avatar (in this case no additional metadata should be sent)
ID_DOC_PHOTO Photo from an ID doc (like a photo from a passport) (No additional metadata should be sent)
AGREEMENT Agreement of some sort, e.g. for processing personal info
CONTRACT Some sort of contract
DRIVERS_TRANSLATION Translation of the driving license required in the target country
INVESTOR_DOC A document from an investor, e.g. documents which disclose assets of the investor
VEHICLE_REGISTRATION_CERTIFICATE Certificate of vehicle registration
INCOME_SOURCE A proof of income
PAYMENT_METHOD Entity confirming payment (like bank card, crypto wallet, etc)
BANK_CARD A bank card, like Visa or Maestro
COVID_VACCINATION_FORM COVID vaccination document (may contain the QR code)
OTHER Should be used only when nothing else applies

# Getting applicant data

During the verification, we also extract data from the applicant's ID docs. To get the full structured view of an applicant, you should perform the following request.

GET /resources/applicants/{applicantId}/one

or

GET /resources/applicants/-;externalUserId={externalUserId}/one

You may want to use the second request when you don't know an applicant ID yet. E.g., when WebSDK created an applicant for you and we called your webhook endpoint.

# REQUEST ARGUMENTS
Name Type Required Description
applicantId String Yes Applicant id
externalUserId String Yes User id in your system
# RESPONSE
Response Code Response Reason
200 Applicant entity
400 Error JSON Invalid applicant id provided
404 Error JSON Applicant not found

The existence of each field depends on the documents submitted for verification and the regulations of verification.

It's basically an applicant that you've created (or we've created for you, e.g., via WebSDK) with augmented information. The info.idDocs[] array of objects contains information extracted from applicants’ documents which can be useful in cases where a verification has been completed.

To make sure that you've fetched data from a relevant document you can get what idDocType and country was approved using this method.

Note that particular data is shown only if it was recognized from the document or provided by user.

# RESPONSE ATTRIBUTES
Name Type Optional Description
id String No An applicantId.
inspectionId String No Inspection ID.
externalUserId String No An applicant ID on the client side, should be unique.
sourceKey String Yes If you want to separate your clients that send applicants, provide this field to distinguish between them. It also shows up at the webhook payloads.
email String Yes Applicant email.
phone String Yes Applicant phone number.
lang String Yes The language in which the applicant should see the result of verification in ISO 639-1 format.
metadata Array of objects Yes Additional information that is not displayed to the end user ( Example: [{"key": "keyFromClient", "value": "valueFromClient"}] ).
fixedInfo Object Yes Basic information about the applicant that we shouldn't change from our side but cross-validate it with data recognized from documents. Has the same attributes as info.
createdAt Date No Time and date of applicant creation.
requiredIdDocs Object Yes Object that describes the set of required documents and data for applicant to upload and pass verification.
review Object No Object that describes current applicant status.
questionnaires Array of objects Yes Contains data about filled questionnaire. More info is here.
# fixedInfo and info ATTRIBUTES
Name Type Optional Description
firstName String Yes First name.
lastName String Yes Last name.
middleName String Yes Middle name.
firstNameEn String Yes Automatic transliteration of the first name.
lastNameEn String Yes Automatic transliteration of the last name.
middleNameEn String Yes Automatic transliteration of the middle name.
legalName String Yes Legal name.
gender String Yes Sex of a person (M or F).
dob String Yes Date of birth (format YYYY-mm-dd, e.g. 2001-09-25).
placeOfBirth String Yes Place of birth.
country String Yes Alpha-3 country code (e.g. DEU or GBR) (Wikipedia).
nationality String Yes Alpha-3 country code (Wikipedia).
addresses Array of objects Yes List of addresses.
idDocs Array of objects Yes Represents the set of data recognized from uploaded documents.
tin String Yes Tax Identification Number.
# addresses ELEMENTS FIELDS
Name Type Optional Description
country String Yes Alpha-3 country code.
postCode String Yes Postal code.
town String Yes Town or city name.
street String Yes Street name.
subStreet String Yes Additional street information.
state String Yes State name if applicable.
# Example request
# Example response

Here, you can find examples of recognition results for all supported document types.

# Changing provided info (fixedInfo)

If you'd like to alter data that you've provided us to cross-check it with documents, you can issue a PATCH request instead of creating a new applicant, which is highly discouraged. This method patches the fields in the fixedInfo key of the applicant.

PATCH /resources/applicants/{applicantId}/fixedInfo

# REQUEST ARGUMENTS

The body of fixedInfo must contain all those fields that you'd like to change.

Name Type Required Description
#{body} Object Yes Field in the fixedInfo attribute that should be changed
applicantId String Yes Applicant ID

You can take a look at available fixedInfo attributes here.

# Example request

# Getting verification results

When we are talking about verification results, we typically mean two labels:

Value Description
GREEN Everything is fine
RED Some violations found

Once verification is finished, we will send you a POST request with JSON payload to the URL provided to us while integrating. Often times, there are different URLs for staging and production environments. We may send several types of webhooks, but the one of interest here is the applicantReviewed webhook. This type of webhook is expected to arrive at average after 3-5 minutes, but can take up to 24 hours, in theory.

If for some reason you missed the webhooks, don't worry: we make a note of everything that was attempted to be sent, and can resend failed webhooks at any point in time. If webhook request fails, we attempt to resend it four times: after 5 minutes, 1 hour, 5 and 18 hours until request succeeds.

Check createdAtMs field of webhook payload to make sure that you're getting relevant applicant status.

We recommend that you wait for the webhook no more than a day, and then send a request to our server for information about the status of the applicant.

Also you can monitor webhook statuses and resend them manually on the API Health page of the dashboard.

Please note that the rejectLabels are only needed to analyze your statistics. They are generalized, so you should not use them to generate comments for your end user.

Webhook contains a reviewResult field that contains extra information, please see example. An important field is reviewAnswer, which contains a final highly trusted answer. Additionally, in cases of the RED answer, the rejectLabels field contains one or more of the following machine-readable constants:

Value Default reviewRejectType Description
FORGERY FINAL Forgery attempt has been made
DOCUMENT_TEMPLATE FINAL Documents supplied are templates, downloaded from internet
LOW_QUALITY RETRY Documents have low-quality that does not allow definitive conclusions to be made
SPAM FINAL An applicant has been created by mistake or is just a spam user (irrelevant images were supplied)
NOT_DOCUMENT RETRY Documents supplied are not relevant for the verification procedure
SELFIE_MISMATCH FINAL A user photo (profile image) does not match a photo on the provided documents
ID_INVALID RETRY A document that identifies a person (like a passport or an ID card) is not valid
FOREIGNER FINAL When a client does not accept applicants from a different country or e.g. without a residence permit
DUPLICATE FINAL This applicant was already created for this client, and duplicates are not allowed by the regulations
BAD_AVATAR RETRY When avatar does not meet the client's requirements
WRONG_USER_REGION FINAL When applicants from certain regions/countries are not allowed to be registered
INCOMPLETE_DOCUMENT RETRY Some information is missing from the document, or it's partially visible
BLACKLIST FINAL User is blocklisted by our side
BLOCKLIST FINAL User is blocklisted by your side
UNSATISFACTORY_PHOTOS RETRY There were problems with the photos, like poor quality or masked information
DOCUMENT_PAGE_MISSING RETRY Some pages of a document are missing (if applicable)
DOCUMENT_DAMAGED RETRY Document is damaged
REGULATIONS_VIOLATIONS FINAL Regulations violations
INCONSISTENT_PROFILE FINAL Data or documents of different persons were uploaded to one applicant
PROBLEMATIC_APPLICANT_DATA RETRY Applicant data does not match the data in the documents
ADDITIONAL_DOCUMENT_REQUIRED RETRY Additional documents required to pass the check
AGE_REQUIREMENT_MISMATCH FINAL Age requirement is not met (e.g. cannot rent a car to a person below 25yo)
EXPERIENCE_REQUIREMENT_MISMATCH FINAL Not enough experience (e.g. driving experience is not enough)
CRIMINAL FINAL The user is involved in illegal actions
WRONG_ADDRESS RETRY The address from the documents doesn't match the address that the user entered
GRAPHIC_EDITOR RETRY The document has been edited by a graphical editor
DOCUMENT_DEPRIVED RETRY The user has been deprived of the document
COMPROMISED_PERSONS FINAL The user does not correspond to Compromised Person Politics
PEP FINAL The user belongs to the PEP category
ADVERSE_MEDIA FINAL The user was found in the adverse media
FRAUDULENT_PATTERNS FINAL Fraudulent behavior was detected
SANCTIONS FINAL The user was found on sanction lists
NOT_ALL_CHECKS_COMPLETED RETRY All checks were not completed
FRONT_SIDE_MISSING RETRY Front side of the document is missing
BACK_SIDE_MISSING RETRY Back side of the document is missing
SCREENSHOTS RETRY The user uploaded screenshots
BLACK_AND_WHITE RETRY The user uploaded black and white photos of documents
INCOMPATIBLE_LANGUAGE RETRY The user should upload translation of his document
EXPIRATION_DATE RETRY The user uploaded expired document
UNFILLED_ID RETRY The user uploaded the document without signatures and stamps
BAD_SELFIE RETRY The user uploaded a bad selfie
BAD_VIDEO_SELFIE RETRY The user uploaded a bad video selfie
BAD_FACE_MATCHING RETRY Face check between document and selfie failed
BAD_PROOF_OF_IDENTITY RETRY The user uploaded a bad ID document
BAD_PROOF_OF_ADDRESS RETRY The user uploaded a bad proof of address
BAD_PROOF_OF_PAYMENT RETRY The user uploaded a bad proof of payment
SELFIE_WITH_PAPER RETRY The user should upload a special selfie (e.g. selfie with paper and date on it)
FRAUDULENT_LIVENESS FINAL There was an attempt to bypass liveness check
OTHER RETRY Some unclassified reason
REQUESTED_DATA_MISMATCH RETRY Provided info doesn't match with recognized from document data
OK RETRY Custom reject label
COMPANY_NOT_DEFINED_STRUCTURE RETRY Could not establish the entity's control structure
COMPANY_NOT_DEFINED_BENEFICIARIES RETRY Could not identify and duly verify the entity's beneficial owners
COMPANY_NOT_VALIDATED_BENEFICIARIES RETRY Beneficiaries are not validated
COMPANY_NOT_DEFINED_REPRESENTATIVES RETRY Representatives are not defined
COMPANY_NOT_VALIDATED_REPRESENTATIVES RETRY Representatives are not validated
APPLICANT_INTERRUPTED_INTERVIEW RETRY On Video Ident call user refused to finish interview
DOCUMENT_MISSING RETRY On Video Ident call user refused to show or didn't have required documents
UNSUITABLE_ENV RETRY On Video Ident call user is either not alone or nor visible
CONNECTION_INTERRUPTED RETRY Video Ident call connection was interrupted
UNSUPPORTED_LANGUAGE FINAL Unsupported language for video identification
THIRD_PARTY_INVOLVED FINAL The user is doing verification from a third party for a fee
CHECK_UNAVAILABLE RETRY The database is not available
INCORRECT_SOCIAL_NUMBER RETRY The user provided an incorrect social number (SSN, for example)

Finally, the reviewRejectType field tells about the rejection type (if the RED answer is given). The following values are possible:

Value Description
FINAL Final reject, e.g. when a person is a fraudster, or a client does not want to accept such kinds of clients in his/her system
RETRY Decline that can be fixed, e.g. by uploading an image of better quality
# Example webhook payload with 'RED' answer
# Example webhook payload with 'GREEN' answer

# Applicant life-cycle

  • When creating an applicant, its status becomes init.
  • After uploading all required documents the status becomes pending.

Automatic status change (to pending) only works within our SDKs and when all necessary documents are uploaded.

  • Within API Integration there is a special mode, when a client must manually signal to us when an applicant is ready to be reviewed.
  • After the verification process is completed, the status of the applicant will change to completed.

# Getting applicant review status

It is recommended that you use this method if you are using WebSDK or MobileSDK since SDKs will show rejection reasons and comments within their screens. But if you still need to fetch rejection comments, it's possible using this method below.

GET /resources/applicants/{applicantId}/status

# REQUEST ARGUMENTS
Name Type Required Description
applicantId String Yes Applicant ID
# RESPONSE
Name Type Optional Description
createDate Date No Date of creation of the applicant.
reviewDate Date Yes Date of check ended.
startDate Date Yes Date of check started.
reviewResult Object Yes Field that contains extra information on applicant verification results.
reviewStatus String No Current status of an applicant.
levelName String Yes Name of user verification steps configuration (level).
attemptCnt Integer Yes Number of new review attempts on the same level.
# reviewResult ATTRIBUTES
Name Type Optional Description
reviewAnswer String No Provide GREEN or RED to simulate answer you need.
rejectLabels List of Strings Yes Reasons for rejections.
reviewRejectType String Yes Choose FINAL or RETRY to simulate other types of rejections.
clientComment String Yes A human-readable comment that should not be shown to the end user.
moderationComment String Yes A human-readable comment that can be shown to the end user.
buttonIds List of Strings Yes A list of button IDs that were used for applicant rejection.

The reviewStatus can be one of the following:

Value Description
init Initial registration has started. A client is still in the process of filling out the applicant profile. Not all required documents are currently uploaded.
pending An applicant is ready to be processed.
prechecked The check is in a half way of being finished.
queued The checks have been started for the applicant.
completed The check has been completed.
onHold Applicant waits for a final decision from compliance officer (manual check was initiated) or waits for all beneficiaries to pass KYC in case of company verification.

Please note that reviewAnswer has an impact on an applicant only with reviewStatus : completed.

# Example request
# Example response

# Getting applicant verification steps status

It is recommended that you use this method if you'd like to fetch information about documents or separate verification step results.

Some rejection types may not affect document status, so don't forget to check overall applicant review status and answer.

GET /resources/applicants/{applicantId}/requiredIdDocsStatus

# REQUEST ARGUMENTS
Name Type Required Description
applicantId String Yes Applicant ID
# RESPONSE

A breakdown for each step. Each step contains an array of images' IDs (could be several, e.g., if two sides were uploaded).

If an applicant is rejected with a FINAL value of reviewRejectType within webhook payload or at the answer on that endpoint, the response from requiredIdDocsStatus may not provide much useful information. If you need to access the moderationComment or clientComment for a FINAL rejection, it's better to retrieve that information from the webhook or applicant data payload.

To obtain images with IDs from the response, you can issue a request as described here.

# Example request:

Some steps may not have review results, it depends on the review of some other step. e.g. PROOF_OF_RESIDENCE step depends on IDENTITY step if it exists, so PROOF_OF_RESIDENCE may not have been checked until IDENTITY is approved.

# Example response:

# Clarifying the reason of rejection

To retrieve the rejection reasons for a document and an applicant, we assign a specific buttonId for each rejection reason using the method provided. When an applicant is rejected, it's possible to get exact reasons via the following request:

GET /resources/moderationStates/-;applicantId={applicantId}

# REQUEST ARGUMENTS
Name Type Required Description
applicantId String Yes Applicant ID
# RESPONSE
Name Type Optional Description
imagesStates Object Yes Structure contains imageId and buttonId which indicate rejection reason
applicantState Object Yes Structure contains buttonId which indicate rejection reason of applicant data or applicant itself

imagesStates and applicantState are present in the response only when the applicant is being rejected.

# Available buttonId

# Example request
# Example response

# Requesting an applicant check

You can programmatically ask us to re-check an applicant in cases where you or your user believe that our system made a mistake, or you're sending us documents via API and would like us to perform a check. To initiate a re-check, simply move the applicant to the pending state by performing the following request.

POST /resources/applicants/{applicantId}/status/pending?reason={reason}

# REQUEST ARGUMENTS
Name Type Required Description
applicantId String Yes Applicant ID
reason String No A note indicating the reason for checking the applicant profile
# RESPONSE

In case of HTTP response 200, you may ignore the response body.

# Example request
# Example response

# Requesting an applicant check with different verification type

If you plan to use this endpoint, please inform the Sum&Substance team, because this requires certain settings on our side.

You can request applicant check with flow that differs from your usual checks.

POST /resources/applicants/{applicantId}/status/pending?reasonCode={reasonCode}

# REQUEST ARGUMENTS
Name Type Required Description
applicantId String Yes Applicant ID
reasonCode String Yes Code of the new flow

Provide us a reasonCode, so we could set up different flows on our side.

# RESPONSE

In case of HTTP response 200, you may ignore the response body.

# Example request
# Example response

# Getting document images

Since a user may re-upload images several times and, for example, also change a passport photo to an ID card, it might be tricky for you to understand which ones actually made the applicant pass or fail.

If you are interested in receiving images that were part of the final verification, you should use this method.

GET /resources/inspections/{inspectionId}/resources/{imageId}

# REQUEST ARGUMENTS
Name Type Required Description
inspectionId String Yes Inspection ID (it's a part of an applicant)
imageId String Yes Image ID (see above)
# RESPONSE

Binary content representing a document. The Content-Type response header precisely describes the response mime-type. The full list of mime-types and file formats you can find here.

# Example request:

# Adding an applicant to blocklist

If for some reason you need to add an applicant to the blocklist, you can use this endpoint. It is necessary to add the reason for adding the applicant to the blocklist.

POST /resources/applicants/{applicantId}/blacklist

We advise checking if the applicant is not already in our blocklist first (check for BLACKLIST and BLOCKLIST values within rejectLabels[] array of that applicant). In case you'll try to add a blocklisted applicant to blocklist again we'll throw a 400 http status with 5000 error code.

# REQUEST ARGUMENTS
Name Type Required Description
applicantId String Yes Applicant ID
note String Yes Reason or note for the applicant added to the blocklist (URL encoded)
# RESPONSE

The answer will contain the applicant added to the blocklist.

# Example request
# Example response

# Sharing applicants between partner services

Sharing Flow

Sometimes our clients partner with each other and want to simplify and speed up verification for their users when they have passed KYC once via Sum&Substance.

Assume a user A passed KYC in service X, and is now registering at the partner service Y. If X agrees to share information about A with Y, it can be done as follows:

  • X generates a share token and passes it to Y
  • Y calls our API with the share token and receives an applicant for user A (with all its data and documents) on their account

Contact our team before using data sharing functionality in order to sign tripartite agreement on personal data sharing with you, Sumsub and your partner service.

# Generating a share token

POST /resources/accessTokens/-/shareToken?applicantId={applicantId}&forClientId={clientId}

# Example of getting a share token (done by X)
# Example response

Make sure your integration code does not validate or analyze the access token content, as the format is not fixed and may undergo further changes in the future. The token must be treated as an arbitrary string with the maximum length of 1KB.

# REQUEST ARGUMENTS
Name Type Required Description Default
applicantId String Yes Applicant ID on service X
forClientId String Yes Client ID of service Y (ask them for this information). You can find your clientId at the dashboard in the applicant profile - field of "Created for" and at response of getting applicant data request - clientId
ttlInSecs Integer No Time to live in seconds 1200
# RESPONSE

Share token in the token field of the returned JSON (see example).

# Importing an applicant

# Example of importing an applicant (done by Y)
# Example response

POST /resources/applicants/-/import?shareToken={shareToken}

# REQUEST ARGUMENTS
Name Type Required Description Default
shareToken String Yes Share token generated by X
resetIdDocSetTypes String No Specify one or few comma-separated document types if an applicant has to resubmit those documents to be verified. Examples, SELFIE, IDENTITY, etc.
trustReview Boolean No If you trust your partner's check result, then you should use true. If it is false, then the applicant will be rechecked. false
userId String No Sets your own externalUserId for the imported applicant. In case of empty value we'll generate a random one.
levelName String No Sets specified levelName to imported applicant and sets init in case not all required documents present.
# RESPONSE

Applicant entity in service Y. Note a new applicant ID will be returned to the response. Bind it in your system as needed.

Please note that share tokens will be invalidated once used.

# Resetting a single verification step

For some cases, it's required for user to pass an already passed verification step - the method below will make the step inactive for SDK to run it again and collect new data.

Please note that not all verification steps are possible to reset using the method below.

POST /resources/applicants/{applicantId}/resetStep/{idDocSetType}

# REQUEST ARGUMENTS
Name Type Required Description
applicantId String Yes Applicant ID
idDocSetType String Yes Step name to reset
# AVAILABLE STEPS TO RESET
Name Description
PHONE_VERIFICATION Phone verification step
EMAIL_VERIFICATION Email verification step
QUESTIONNAIRE Questionnaire
APPLICANT_DATA Applicant data
IDENTITY Identity step
IDENTITY2 2nd Identity step
IDENTITY3 3rd Identity step
IDENTITY4 4th Identity step
PROOF_OF_RESIDENCE Proof of residence
PROOF_OF_RESIDENCE2 2nd Proof of residence
SELFIE Selfie step
SELFIE2 2nd Selfie step
# Example request
# Example response

# Resetting an applicant

In very rare cases, it is required to change the status of the applicant to init. For example, if a user has contacted support with a request to re-pass verification from scratch with new documents.

Please note that resetting an applicant with fraudulent patterns is not a safe option. You can make sure that it's safe to reset by checking if applicant does not have any of these rejectLabels: FORGERY, SELFIE_MISMATCH, BLACKLIST, BLOCKLIST, INCONSISTENT_PROFILE, FRAUDULENT_PATTERNS.

If you plan to use this endpoint, please inform the Sum&Substance team, as we may be able to suggest another flow for your case.

POST /resources/applicants/{applicantId}/reset

# REQUEST ARGUMENTS
Name Type Required Description
applicantId String Yes Applicant ID
# Example request
# Example response

# Changing top-level info

This method patches top-level applicant info like email, externalUserId, sourceKey, type for specified applicantId. The body must contain only those fields that you intend to change. Null fields will be ignored.

PATCH /resources/applicants/

# REQUEST BODY
Name Type Required Description
id String Yes Applicant ID
externalUserId String No New External ID for an applicant
email String No New email
phone String No New phone number
sourceKey String No New sourceKey
lang String No The language in which the applicant should see result of verification
questionnaires Array of objects No Questionnaire configuration with answers
metadata Array of objects No Additional information about user at custom fields
# Example request
# Example response

# Deactivating applicant profile

In some cases, you may need to deactivate an applicant, so no action could be performed with the applicant profile, and all applicant data from this profile will be ignored when checking new applicants for duplicates. You can later reactivate the profile by specifying the activated status in the request.

To deactivate/reactivate the applicant profile, use the following method:

PATCH /resources/applicants/{applicantId}/presence/{status}

# REQUEST ARGUMENTS
Name Type Required Description
applicantId String Yes Applicant ID.
status String Yes Possible values:
  • deactivated — specify to deactivate the applicant profile.
  • activated — specify to reactivate the applicant profile.
# Example request

The response would include the array of objects containing the applicant data. The set of data depends on the permissions you have in the system.

Mind the “deleted” parameter value which indicates the request result:

  • true — applicant profile is deactivated.
  • false — applicant profile is activated.
# Example response

# Import applicants

This method is intended for data import. For example, in case you already got a lot of applicants and want to recheck or maintain data consistency.

If you plan to use this endpoint, please inform the Sum&Substance team, as we may be able to suggest another flow for your case.

POST /resources/applicants/-/ingestCompleted?levelName={levelName}

# REQUEST ARGUMENTS
Name Type Required Description
#{body} Object Yes An object representing an applicant (see example).
levelName String No Name of user verification steps configuration from the dashboard.

Please note that the request body structure may be unique depending on the case, please inform the Sum&Substance team before using this method.

# Example request
# Example response

# Import applicant with images by archive

This method is intended for applicant data and images import with its review results. Archive will be processed and as a result an applicant with approved documents within it will be created.

POST /resources/applicants/-/applicantImport

# REQUEST ARGUMENTS
Name Type Required Description
#{body} Binary Yes Zip archive with data to import.

Within the request body zip archive should be sent. For the service to be able to process an archive it should contain

  • applicant.json file that has applicant entry with review, similar to the body for request above
  • directories, that contain an image to upload and applicantIdDoc.json file with info on the document (one per directory)


You can find archive example here.

# Example request
# Example applicant.json file content

Make sure to specify .review.levelName to create applicant with a list of required documents.

# Example applicantIdDoc.json file content

applicantIdDoc.json resemble the same object that is used as metadata to upload an image to the applicant.

Object values of idDocType and country should always present within applicantIdDoc.json file.

# Example response

# Set risk level for the applicant

This method allows you to set a risk level for your applicant by your own criteria.

POST /resources/applicants/{applicantId}/riskLevel/entries

Please note that App Token should have special permission to make this request (contact the Sum&Substance tech team).

# REQUEST BODY
Name Type Required Supported values
comment String Yes Any string
riskLevel String Yes unknown/low/medium/high
# Example request
# Example response

# Adding custom applicant tags

Use this method to assign custom tags to applicant profiles. Also, create new tags in the Global settings section of the dashboard

POST /resources/applicants/{applicantId}/tags

# REQUEST ARGUMENTS
Name Type Required Supported values
applicantId String Yes Applicant ID
#{body} Array Yes Array of custom tags strings
# Example request
# Example response

# Marking image as inactive (deleted)

This method allows you to mark uploaded image as deleted so during initialization SDK screen would ask for a new one. It can be used in cases you want your users to re-upload document that was previously approved via SDK.

Note, that trying to mark images as inactive while the applicant is being processed at the moment (pending, prechecked, queued statuses) will result in error and 304 HTTP status.

DELETE /resources/inspections/{inspectionId}/resources/{imageId}

# REQUEST ARGUMENTS
Name Type Required Description
inspectionId String Yes Inspection id
imageId String Yes Image ID (more info here)
# QUERY PARAMETERS
Name Type Required Default Description
revert Boolean No false Set true to revert inactivity of the image
# RESPONSE

In case of HTTP response 200, you may ignore the response body.

# Example request
# Example response

# Setting review for applicant on the sandbox

On a sandbox you can try receiving results via applicantReviewed webhooks by triggering the following endpoint (provide either GREEN or RED to simulate a positive or negative answer, correspondingly):

POST /resources/applicants/{applicantId}/status/testCompleted

# REQUEST ARGUMENTS
Name Type Required Description
#{body} Object Yes Please see example
applicantId String Yes Applicant ID
# REQUEST BODY
Name Type Required Description
reviewAnswer String Yes Provide GREEN or RED to simulate answer you need
rejectLabels Array of Strings Yes Reasons for rejections
reviewRejectType String No Choose FINAL or RETRY to simulate other types of rejections
clientComment String No A human-readable comment that should not be shown to the end user
moderationComment String No A human-readable comment that can be shown to the end user
# RESPONSE

In the case of HTTP response 200, you may ignore the response body.

# Example request to change the applicant status to GREEN
# Example request to change the applicant status to RED
# Example response

# Webhooks

A webhook is an HTTP POST request sent to a target you define at Dev space dashboard.

Please note that we do not send any info to endpoints using http, only https.

Not receiving an answer from webhook endpoint for more than 15 seconds will be considered as a timeout, and our service will try to resend that webhook again later.

Supported versions of TLS protocol are 1.2 or higher.

The Sandbox does not automatically check the applicants. If you need to change the status of the applicant or get webhook on the test server, you can do it by yourself.

Please note that there is a limit on the webhook number: 20 webhook objects max.

We can send webhooks not only via HTTP request, but also as notifications in Slack, Telegram or via email.

In case for some reason you missed the webhooks, don't worry: we make a note of everything that was attempted to be sent, and can resend failed webhooks at any point in time. When webhook http request fails, we attempt to resend it four times: after 5 minutes, 1 hour, 5 and 18 hours until request succeeds.

Check createdAtMs field of webhook payload to make sure that you're getting relevant applicant status.

# Webhook types

The types of webhooks we send depend on the settings.

Value Description
applicantReviewed When verification is completed. Contains the verification result. More information about this type of webhook can be found here.
applicantPending When a user uploaded all the required documents and the applicant's status changed to pending.
applicantCreated When an applicant is created.
applicantOnHold Processing of the applicant is paused for an agreed reason.
applicantPersonalInfoChanged Applicant's personal info has been changed.
applicantPrechecked When primary data processing is completed.
applicantDeleted Applicant has been permanently deleted.
applicantLevelChanged Applicant level has been changed.
videoIdentStatusChanged Status of Video Ident type of verification has been changed.
applicantReset Applicant has been reset: applicant status changed to init and all documents were set as inactive. You can find more info here.
applicantActionPending Applicant action status changed to pending. More info about applicant actions you may find here.
applicantActionReviewed Applicant action verification has been completed. More info about applicant actions you may find here.
applicantActionOnHold Applicant action verification has been paused for an agreed reason. More info about applicant actions you may find here.
applicantWorkflowCompleted Workflow has been completed for an applicant.

Example applicantReviewed webhook payload you can find here.

# Webhook payload attributes
Name Type Optional Description
applicantId String No Applicant ID.
inspectionId String No Inspection ID (it's a part of an applicant).
correlationId String No This id uniquely identifies an event on our side.
levelName String Yes Applicant level name.
externalUserId String No Unique user Id on your side.
type String No Webhook type.
sandboxMode Boolean No True if the webhook was sent from the Sandbox mode.
reviewStatus String No Current status of an applicant. More info can be found here.
createdAtMs Date No Date and time of webhook creation (format yyyy-MM-dd HH:mm:ss.fff, e.g. 2021-05-14 16:00:25.032) in UTC.
applicantType String Yes Type of the applicant e.g. company/individual.
reviewResult Object Yes Field that contains extra information on applicant verification results.
applicantMemberOf Array of objects Yes Contains the list of company applicantIds that current applicant belongs as a beneficiary.
videoIdentReviewStatus String Yes Status of the videoIdent call.
applicantActionId String Yes Id of an applicant action.
externalApplicantActionId String Yes Unique action Id on your side.
clientId String No Unique identifier of you as our client.

We don't send any personal data via webhooks. You can fetch all recognized data using these methods via API.

# Example applicantCreated webhook payload:
# Example applicantPending webhook payload:
# Example applicantOnHold webhook payload:
# Example applicantPersonalInfoChanged webhook payload:
# Example applicantPrechecked webhook payload:
# Example applicantDeleted webhook payload:
# Example applicantLevelChanged webhook payload:
# Example videoIdentStatusChanged webhook payload when video call has been initialized:
# Example videoIdentStatusChanged webhook payload when video call has been completed:
# Example applicantReset webhook payload:
# Example applicantWorkflowCompleted webhook payload:

If you're not receiving webhooks from us try to check your endpoints first with SSL Labs or Docker.

# Verifying webhook sender

It's better to not rely on our IP addresses for whitelisting them as webhook sender because they can change from time to time.

In order to make sure that a webhook is sent by Sumsub, we have the possibility to sign it with the HMAC algorithm.

If you want to utilize this feature, set a Secret Key value for each webhook in the Dashboard.

We also send the additional X-Payload-Digest-Alg header which specifies one of the following algorithms to use:

  • HMAC_SHA1_HEX (Legacy, deprecated)
  • HMAC_SHA256_HEX (default upon creating a new webhook)
  • HMAC_SHA512_HEX

Choose the algorithm when configuring webhooks in the Dashboard.

To verify that a webhook is sent by Sumsub:

  1. Get a webhook x-payload-digest header value and payload as it is without any alteration or converting to json.
  2. Receive the HTTP webhook body in bytes.
  3. Calculate digest with raw webhook payload in bytes and the HMAC algorithm specified in the x-payload-digest-alg header.
  4. Compare x-payload-digest header value with calculated digest.

In order to check that you compute the digest the same way we do, you can call the following endpoint. This endpoint takes text as a body and cannot process JSON payload as for real cases.

POST /resources/inspectionCallbacks/testDigest?secretKey={secretKey}

# REQUEST ARGUMENTS
Name Type Required Description
#{body} String Yes Any text
secretKey String Yes A secret key that might be used for signing
# RESPONSE
Value Description
digest Result of the calculation

Please test your webhook before sending its URL to us. At a minimum, it should not give a 500 HTTP response or require any sort of authorization.

# Example request to get digest
# Example response
# Example request to client's endpoint
# Example of computing digest
Last Updated: 11/22/2023, 7:23:33 AM