Sandbox

The Yoti Identity Verification (IDV) Sandbox is an isolated testing environment for validating your integration with mock data and simulated verification outcomes.

What you can do

  • Test end-to-end flows without real user data

  • Simulate different verification outcomes (approvals, rejections, extractions)

  • Use standard Yoti backed SDKs (no separate sandbox SDK)

  • Manually test using the user view, or automate testing (via built-in agent) by bypassing the user view.

Key differences

Sandbox uses the same SDKs as production but requires:

  • Sandbox URL: https://api.yoti.com/sandbox/idverify/v1

  • Sandbox keys from Yoti Hub

  • Predefined successful responses by default

  • Optional response configuration (see Configure response)


Before you start

Ensure you have submitted your Yoti Hub organisation for verification and have generated sandbox keys.

Sandbox keys


Install the SDK

Install the Yoti SDK using your language's package manager. The same SDK is used for both production and sandbox environments.

npm install -S -E yoti
// If you are using Maven, add the following dependency:
<dependency>
<groupId>com.yoti</groupId>
<artifactId>yoti-sdk-api</artifactId>
<version>3.12.0</version>
</dependency>

// If you are using Gradle, add the following dependency:
implementation group: 'com.yoti', name: 'yoti-sdk-api', version: '3.12.0'
// Get the Yoti PHP SDK library via a Composer package
composer require yoti/yoti-php-sdk
pip install yoti
// To install the Yoti NuGet package, enter the following command
// from NuGet Package Manager Console in Visual Studio:
Install-Package Yoti
// Add to go.mod:
require github.com/getyoti/yoti-go-sdk/v3

// Or download via terminal:
go get "github.com/getyoti/yoti-go-sdk/v3"

Step 1: Initialise the client

Initialise the Yoti client with your sandbox credentials and point it to the sandbox URL.

Sandbox URL: https://api.yoti.com/sandbox/idverify/v1

const { IDVClient } = require('yoti');
const fs = require('fs');

const SANDBOX_CLIENT_SDK_ID = 'YOUR_SANDBOX_SDK_ID';
const SANDBOX_PEM = fs.readFileSync('/path/to/your-sandbox-pem-file.pem', 'utf8');

// Initialise client with sandbox URL
const idvClient = new IDVClient(
SANDBOX_CLIENT_SDK_ID,
SANDBOX_PEM,
{ apiUrl: 'https://api.yoti.com/sandbox/idverify/v1' }
);
// Point the Doc Scan client at the sandbox by setting environment variable
System.setProperty("yoti.docs.url", "https://api.yoti.com/sandbox/idverify/v1");

import com.yoti.api.client.ClassPathKeySource;
import com.yoti.api.client.docs.DocScanClient;

...
String SANDBOX_CLIENT_SDK_ID = "YOUR_SANDBOX_SDK_ID";
String SANDBOX_PEM_PATH = "/path/to/your-sandbox-pem-file.pem";

DocScanClient docScanClient = DocScanClient.builder()
.withClientSdkId(SANDBOX_CLIENT_SDK_ID)
.withKeyPairSource(ClassPathKeySource.fromClasspath(SANDBOX_PEM_PATH))
.build();
<?php

use Yoti\DocScan\DocScanClient;

$SANDBOX_CLIENT_SDK_ID = 'YOUR_SANDBOX_SDK_ID';
$SANDBOX_PEM_PATH = '/path/to/your-sandbox-pem-file.pem';

$docScanClient = new DocScanClient(
$SANDBOX_CLIENT_SDK_ID,
$SANDBOX_PEM_PATH,
['api.url' => 'https://api.yoti.com/sandbox/idverify/v1']
);
from yoti_python_sdk.doc_scan import DocScanClient

SANDBOX_CLIENT_SDK_ID = 'YOUR_SANDBOX_SDK_ID'
SANDBOX_PEM_PATH = '/path/to/your-sandbox-pem-file.pem'

doc_scan_client = DocScanClient(
SANDBOX_CLIENT_SDK_ID,
SANDBOX_PEM_PATH,
api_url="https://api.yoti.com/sandbox/idverify/v1"
)
using System.IO;
using System.Net.Http;
using Yoti.Auth;
using Yoti.Auth.DocScan;

...
const string SANDBOX_CLIENT_SDK_ID = "YOUR_SANDBOX_SDK_ID";
const string SANDBOX_PEM_PATH = "/path/to/your-sandbox-pem-file.pem";

StreamReader privateKeyStream = System.IO.File.OpenText(SANDBOX_PEM_PATH);
var key = CryptoEngine.LoadRsaKey(privateKeyStream);

var docScanClient = new DocScanClient(
SANDBOX_CLIENT_SDK_ID,
key,
new HttpClient(),
new Uri("https://api.yoti.com/sandbox/idverify/v1")
);

Step 2: Create a session

Create an identity verification session exactly as you would in production. The session configuration determines what checks and tasks will be performed.

const {
SessionSpecificationBuilder,
RequestedDocumentAuthenticityCheckBuilder,
RequestedLivenessCheckBuilder,
RequestedFaceMatchCheckBuilder,
RequestedTextExtractionTaskBuilder,
SdkConfigBuilder
} = require('yoti');

// Define checks
const documentAuthenticityCheck = new RequestedDocumentAuthenticityCheckBuilder().build();

const livenessCheck = new RequestedLivenessCheckBuilder()
.forStaticLiveness()
.withMaxRetries(3)
.build();

const faceMatchCheck = new RequestedFaceMatchCheckBuilder()
.withManualCheckFallback()
.build();

// Define tasks
const textExtractionTask = new RequestedTextExtractionTaskBuilder()
.withManualCheckFallback()
.build();

// Configure SDK
import com.yoti.api.client.docs.session.create.CreateSessionResult;
import com.yoti.api.client.docs.session.create.SdkConfig;
import com.yoti.api.client.docs.session.create.SessionSpec;
import com.yoti.api.client.docs.session.create.check.RequestedDocumentAuthenticityCheck;
import com.yoti.api.client.docs.session.create.check.RequestedFaceMatchCheck;
import com.yoti.api.client.docs.session.create.check.RequestedLivenessCheck;
import com.yoti.api.client.docs.session.create.task.RequestedTextExtractionTask;

...
// Define checks
RequestedDocumentAuthenticityCheck documentAuthenticityCheck =
RequestedDocumentAuthenticityCheck.builder().build();

RequestedLivenessCheck livenessCheck = RequestedLivenessCheck.builder()
.forStaticLiveness()
.withMaxRetries(3)
<?php

use Yoti\DocScan\Session\Create\SessionSpecificationBuilder;
use Yoti\DocScan\Session\Create\Check\RequestedDocumentAuthenticityCheckBuilder;
use Yoti\DocScan\Session\Create\Check\RequestedLivenessCheckBuilder;
use Yoti\DocScan\Session\Create\Check\RequestedFaceMatchCheckBuilder;
use Yoti\DocScan\Session\Create\Task\RequestedTextExtractionTaskBuilder;
use Yoti\DocScan\Session\Create\SdkConfigBuilder;

// Define checks
$documentAuthenticityCheck = (new RequestedDocumentAuthenticityCheckBuilder())->build();

$livenessCheck = (new RequestedLivenessCheckBuilder())
->forStaticLiveness()
->withMaxRetries(3)
->build();

$faceMatchCheck = (new RequestedFaceMatchCheckBuilder())
->withManualCheckFallback()
->build();

from yoti_python_sdk.doc_scan import (
SessionSpecBuilder,
RequestedDocumentAuthenticityCheckBuilder,
RequestedLivenessCheckBuilder,
RequestedFaceMatchCheckBuilder,
RequestedTextExtractionTaskBuilder,
SdkConfigBuilder
)

# Define checks
document_authenticity_check = RequestedDocumentAuthenticityCheckBuilder().build()

liveness_check = (
RequestedLivenessCheckBuilder()
.for_static_liveness()
.with_max_retries(3)
.build()
)

face_match_check = (
RequestedFaceMatchCheckBuilder()
.with_manual_check_fallback()
.build()
)

# Define tasks
text_extraction_task = (
RequestedTextExtractionTaskBuilder()
.with_manual_check_fallback()
using Yoti.Auth.DocScan.Session.Create;
using Yoti.Auth.DocScan.Session.Create.Check;
using Yoti.Auth.DocScan.Session.Create.Task;

...
// Define checks
var documentAuthenticityCheck = new RequestedDocumentAuthenticityCheckBuilder().Build();

var livenessCheck = new RequestedLivenessCheckBuilder()
.ForStaticLiveness()
.WithMaxRetries(3)
.Build();

var faceMatchCheck = new RequestedFaceMatchCheckBuilder()
.WithManualCheckFallback()
.Build();

// Define tasks
var textExtractionTask = new RequestedTextExtractionTaskBuilder()
.WithManualCheckFallback()
.Build();

// Configure SDK
var sdkConfig = new SdkConfigBuilder()
.WithAllowsCamera()
.WithPresetIssuingCountry("GBR")
.WithSuccessUrl("https://yourdomain.com/success")

Step 3a: Launch the user view (manual testing)

Construct the following URL for manual testing, and render it inside an iFrame:

https://api.yoti.com/sandbox/idverify/v1/web/index.html?sessionID={sessionID}&sessionToken={clientSessionToken}

iFrame example

<iframe
src="https://api.yoti.com/sandbox/idverify/v1/web/index.html?sessionID={sessionID}&sessionToken={clientSessionToken}"
style="height:100vh; width:100%; border:none;"
allow="camera">
</iframe>

Users can upload sample documents and selfies, and the sandbox returns predefined successful responses along with the uploaded image resources.


Step 3b: Use the agent endpoint (automated testing)

Info

The /agent endpoint bypasses the user view and completes sessions programmatically with sample data—ideal for CI/CD and automated tests.

Endpoint

POST https://api.yoti.com/sandbox/idverify/v1/agent

Use Case

Benefit

Automated tests

Skip manual document upload

CI/CD pipelines

Integrate verification in build process

Payload structure

{
"session_id": "YOUR_SESSION_ID",
"client_session_token": "YOUR_SESSION_TOKEN"
}

Code examples

const { RequestBuilder, Payload } = require("yoti");

const payload = {
session_id: sessionId,
client_session_token: clientSessionToken
};

const request = new RequestBuilder()
.withBaseUrl("https://api.yoti.com/sandbox/idverify/v1")
.withPemFilePath(SANDBOX_PEM_PATH)
.withEndpoint("/agent")
.withPayload(new Payload(payload))
.withMethod("POST")
.withQueryParam("sdkId", SANDBOX_CLIENT_SDK_ID)
.build();

// Execute request
const response = await request.execute();
import com.yoti.api.client.spi.remote.call.SignedRequest;
import com.yoti.api.client.spi.remote.call.SignedRequestBuilder;

...
String payload = String.format(
"{\"session_id\":\"%s\",\"client_session_token\":\"%s\"}",
sessionId,
clientSessionToken
);

try {
SignedRequest signedRequest = SignedRequestBuilder.newInstance()
.withKeyPair(SANDBOX_PEM_PATH)
.withBaseUrl("https://api.yoti.com/sandbox/idverify/v1")
.withEndpoint("/agent")
.withPayload(payload.getBytes())
.withHttpMethod("POST")
.withQueryParameter("sdkId", SANDBOX_CLIENT_SDK_ID)
.build();

Response response = signedRequest.execute();
} catch (Exception ex) {
ex.printStackTrace();
<?php
use Yoti\Http\RequestBuilder;
use Yoti\Http\Payload;

$payload = [
'session_id' => $sessionId,
'client_session_token' => $clientSessionToken
];

$request = (new RequestBuilder())
->withBaseUrl('https://api.yoti.com/sandbox/idverify/v1')
->withPemFilePath($SANDBOX_PEM_PATH)
->withEndpoint('/agent')
->withPayload(Payload::fromJsonData($payload))
->withMethod('POST')
->withQueryParam('sdkId', $SANDBOX_CLIENT_SDK_ID)
->build()
->execute();
from yoti_python_sdk.http import SignedRequest
import json
import requests

payload = {
'session_id': session_id,
'client_session_token': client_session_token
}

payload_string = json.dumps(skip_ui).encode()

signed_request = (
SignedRequest
.builder()
.with_pem_file(SANDBOX_PEM_PATH)
.with_base_url("https://api.yoti.com/sandbox/idverify/v1")
.with_endpoint("/agent")
.with_http_method("POST")
.with_param("sdkId", SANDBOX_CLIENT_SDK_ID)
.with_payload(payload_string)
.build()
)

response = signed_request.execute()
using System.IO;
using System.Net.Http;
using System.Text;
using Yoti.Auth.Web;

HttpClient httpClient = new HttpClient();
StreamReader privateKeyStream = System.IO.File.OpenText(SANDBOX_PEM_PATH);

string payload = Newtonsoft.Json.JsonConvert.SerializeObject(new
{
session_id = sessionId,
client_session_token = clientSessionToken
});

byte[] payloadBytes = Encoding.UTF8.GetBytes(serializedRequest);

Uri baseUrl = new UriBuilder("https", "api.yoti.com", 443, "sandbox/idverify/v1").Uri;

Request agentRequest = new RequestBuilder()
.WithStreamReader(privateKeyStream)
.WithBaseUri(baseUrl)
.WithEndpoint("/agent")
.WithHttpMethod(HttpMethod.Post)
.WithContent(payloadBytes)
import (
"encoding/json"
"io/ioutil"
"net/http"
"github.com/getyoti/yoti-go-sdk/v3/requests"
)

key, _ := ioutil.ReadFile(SANDBOX_PEM_PATH)

payload, err := json.Marshal(map[string]string{
"session_id": sessionID,
"client_session_token": clientToken,
})
if err != nil {
return fmt.Errorf("marshal payload: %w", err)
}

request, _ := requests.SignedRequest{
HTTPMethod: http.MethodPost,
BaseURL: "https://api.yoti.com/sandbox/idverify/v1",
Endpoint: "/agent",
Params: map[string]string{
"sdkId": SANDBOX_CLIENT_SDK_ID,
},
Headers: map[string][]string{
"Content-Type": {"application/json"},

Step 4: Retrieve session results

After the session is completed (either via user view or agent endpoint), retrieve the results to verify the outcome.

// Retrieve session result
idvClient.getSession(sessionId).then(session => {
// Session state
const state = session.getState();
// Resources (documents, images)
const resources = session.getResources();
const idDocuments = resources.getIdDocuments();
// Checks
const authenticityChecks = session.getAuthenticityChecks();
const livenessChecks = session.getLivenessChecks();
const faceMatchChecks = session.getFaceMatchChecks();
// Tasks
const textExtractionTasks = session.getTextDataChecks();
// Biometric consent
const biometricConsent = session.getBiometricConsentTimestamp();
console.log('Session completed successfully');
}).catch(error => {
// Retrieve session result
GetSessionResult sessionResult = docScanClient.getSession(sessionId);

// Session state
String state = sessionResult.getState();

// Resources (documents, images)
ResourceContainer resources = sessionResult.getResources();
List<? extends IdDocumentResourceResponse> idDocuments = resources.getIdDocuments();

// Checks
List<AuthenticityCheckResponse> authenticityChecks = sessionResult.getAuthenticityChecks();
List<LivenessCheckResponse> livenessChecks = sessionResult.getLivenessChecks();
List<FaceMatchCheckResponse> faceMatchChecks = sessionResult.getFaceMatchChecks();

// Tasks
List<TextDataCheckResponse> textDataChecks = sessionResult.getTextDataChecks();

// Biometric consent
String biometricConsent = sessionResult.getBiometricConsentTimestamp();
<?php
// Retrieve session result
$sessionResult = $docScanClient->getSession($sessionId);

// Session state
$state = $sessionResult->getState();

// Resources (documents, images)
$resources = $sessionResult->getResources();
$idDocuments = $resources->getIdDocuments();

// Checks
$authenticityChecks = $sessionResult->getAuthenticityChecks();
$livenessChecks = $sessionResult->getLivenessChecks();
$faceMatchChecks = $sessionResult->getFaceMatchChecks();

// Tasks
$textDataChecks = $sessionResult->getTextDataChecks();

// Biometric consent
$biometricConsent = $sessionResult->getBiometricConsentTimestamp();
# Retrieve session result
session_result = doc_scan_client.get_session(session_id)

# Session state
state = session_result.state

# Resources (documents, images)
resources = session_result.resources
id_documents = resources.id_documents

# Checks
authenticity_checks = session_result.authenticity_checks
liveness_checks = session_result.liveness_checks
face_match_checks = session_result.face_match_checks

# Tasks
text_data_checks = session_result.text_data_checks

# Biometric consent
biometric_consent = session_result.biometric_consent_timestamp
// Retrieve session result
GetSessionResult sessionResult = docScanClient.GetSession(sessionId);

// Session state
string state = sessionResult.State;

// Resources (documents, images)
ResourceContainer resources = sessionResult.Resources;
List<IdDocumentResourceResponse> idDocuments = resources.GetIdDocuments();

// Checks
List<AuthenticityCheckResponse> authenticityChecks = sessionResult.GetAuthenticityChecks();
List<LivenessCheckResponse> livenessChecks = sessionResult.GetLivenessChecks();
List<FaceMatchCheckResponse> faceMatchChecks = sessionResult.GetFaceMatchChecks();

// Tasks
List<TextDataCheckResponse> textDataChecks = sessionResult.GetTextDataChecks();

// Biometric consent
DateTime biometricConsent = sessionResult.BiometricConsentTimestamp();

Example sandbox response

Here is a sample JSON response from a finished sandbox session:

{…}

Default responses

The sandbox automatically provides approved results for configured checks using sample data, for example:

  • Document authenticity: APPROVE

  • Liveness: APPROVE

  • Face match: APPROVE

  • Resources: Sample images and text extraction (Document fields)

This lets you test result handling without configuration.


Step 5: (Optional) Configure custom responses

To simulate failures or custom scenarios, refer to the sandbox Configure response guide. This lets you provide mock data such as for text extraction and also simulate partial check approvals or rejections.

You can use the same production SDKs for this as well.


Next steps


Troubleshooting

Session not completing? Verify you're using the sandbox URL: https://api.yoti.com/sandbox/idverify/v1

Authentication errors? Ensure you're using sandbox keys (not production) and haven't opened the PEM file manually.