Sandbox

AI Tools

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 const sdkConfig = new SdkConfigBuilder() .withAllowsCamera() .withPresetIssuingCountry('GBR') .withSuccessUrl('https://yourdomain.com/success') .withErrorUrl('https://yourdomain.com/error') .build(); // Build session specification const sessionSpec = new SessionSpecificationBuilder() .withClientSessionTokenTtl(900) .withResourcesTtl(90000) .withUserTrackingId('some-unique-user-id') .withRequestedCheck(documentAuthenticityCheck) .withRequestedCheck(livenessCheck) .withRequestedCheck(faceMatchCheck) .withRequestedTask(textExtractionTask) .withSdkConfig(sdkConfig) .build(); // Create session idvClient.createSession(sessionSpec) .then((session) => { const sessionId = session.getSessionId(); const clientSessionToken = session.getClientSessionToken(); console.log('Session created:', sessionId); }) .catch((err) => { console.error('Error creating session:', err); });
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) .build(); RequestedFaceMatchCheck faceMatchCheck = RequestedFaceMatchCheck.builder() .withManualCheckFallback() .build(); // Define tasks RequestedTextExtractionTask textExtractionTask = RequestedTextExtractionTask.builder() .withManualCheckFallback() .build(); // Configure SDK SdkConfig sdkConfig = SdkConfig.builder() .withAllowsCamera() .withPresetIssuingCountry("GBR") .withSuccessUrl("https://yourdomain.com/success") .withErrorUrl("https://yourdomain.com/error") .build(); // Build session specification SessionSpec sessionSpec = SessionSpec.builder() .withClientSessionTokenTtl(900) .withResourcesTtl(90000) .withUserTrackingId("some-unique-user-id") .withRequestedCheck(documentAuthenticityCheck) .withRequestedCheck(livenessCheck) .withRequestedCheck(faceMatchCheck) .withRequestedTask(textExtractionTask) .withSdkConfig(sdkConfig) .build(); // Create session CreateSessionResult sessionResult = docScanClient.createSession(sessionSpec); String sessionId = sessionResult.getSessionId(); String clientSessionToken = sessionResult.getClientSessionToken();
<?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(); // Define tasks $textExtractionTask = (new RequestedTextExtractionTaskBuilder()) ->withManualCheckFallback() ->build(); // Configure SDK $sdkConfig = (new SdkConfigBuilder()) ->withAllowsCamera() ->withPresetIssuingCountry('GBR') ->withSuccessUrl('https://yourdomain.com/success') ->withErrorUrl('https://yourdomain.com/error') ->build(); // Build session specification $sessionSpec = (new SessionSpecificationBuilder()) ->withClientSessionTokenTtl(900) ->withResourcesTtl(90000) ->withUserTrackingId('some-unique-user-id') ->withRequestedCheck($documentAuthenticityCheck) ->withRequestedCheck($livenessCheck) ->withRequestedCheck($faceMatchCheck) ->withRequestedTask($textExtractionTask) ->withSdkConfig($sdkConfig) ->build(); // Create session $session = $docScanClient->createSession($sessionSpec); $sessionId = $session->getSessionId(); $clientSessionToken = $session->getClientSessionToken();
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() .build() ) # Configure SDK sdk_config = ( SdkConfigBuilder() .with_allows_camera() .with_preset_issuing_country('GBR') .with_success_url('https://yourdomain.com/success') .with_error_url('https://yourdomain.com/error') .build() ) # Build session specification session_spec = ( SessionSpecBuilder() .with_client_session_token_ttl(900) .with_resources_ttl(90000) .with_user_tracking_id('some-unique-user-id') .with_requested_check(document_authenticity_check) .with_requested_check(liveness_check) .with_requested_check(face_match_check) .with_requested_task(text_extraction_task) .with_sdk_config(sdk_config) .build() ) # Create session session = doc_scan_client.create_session(session_spec) session_id = session.session_id client_session_token = session.client_session_token
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") .WithErrorUrl("https://yourdomain.com/error") .Build(); // Build session specification var sessionSpec = new SessionSpecificationBuilder() .WithClientSessionTokenTtl(900) .WithResourcesTtl(90000) .WithUserTrackingId("some-unique-user-id") .WithRequestedCheck(documentAuthenticityCheck) .WithRequestedCheck(livenessCheck) .WithRequestedCheck(faceMatchCheck) .WithRequestedTask(textExtractionTask) .WithSdkConfig(sdkConfig) .Build(); // Create session CreateSessionResult createSessionResult = docScanClient.CreateSession(sessionSpec); string sessionId = createSessionResult.SessionId; string clientSessionToken = createSessionResult.ClientSessionToken;

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) .WithQueryParam("sdkId", SANDBOX_CLIENT_SDK_ID) .Build(); HttpResponseMessage response = agentRequest.Execute(httpClient).Result;
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"}, "Accept": {"application/json"}, }, Body: payload, }.WithPemFile(key).Request() response, _ := http.DefaultClient.Do(request)

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 => { console.error('Error retrieving session:', 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:

{ "client_session_token_ttl": 433, "session_id": "bfaf87e8-51dc-4ee4-be7b-8d4e3d8e9f41", "state": "COMPLETED", "resources": { "id_documents": [ { "id": "31774db7-8dde-4885-9458-86584f26064b", "document_type": "DRIVING_LICENCE", "issuing_country": "GBR", "pages": [ { "capture_method": "CAMERA", "media": { "id": "74b0437c-e120-4e9f-adb9-56fe71ff09d0", "type": "IMAGE" } } ], "document_fields": { "media": { "id": "3fe2211f-d49d-49c1-9404-c38e9cacc393", "type": "JSON" } }, "document_id_photo": { "media": { "id": "c2aa2b94-b987-4077-ba11-bd92abde5455", "type": "IMAGE" } } } ], "liveness_capture": [ { "id": "508826f9-7e30-40fa-a463-d9b243ff86fd", "liveness_type": "STATIC", "image": { "media": { "id": "4401aff6-b014-4a5a-bf5d-27ce1ce2e27d", "type": "IMAGE" } } } ] }, "checks": [ { "type": "ID_DOCUMENT_AUTHENTICITY", "id": "309f7e0f-5243-4a1f-b5bb-2c72ec10af21", "state": "DONE", "report": { "recommendation": { "value": "APPROVE" }, "breakdown": [ { "sub_check": "document_in_date", "result": "PASS" }, { "sub_check": "fraud_list_check", "result": "PASS" } ] } }, { "type": "LIVENESS", "id": "67c14a2a-2ec4-4d61-8316-c7569ea56d59", "state": "DONE", "report": { "recommendation": { "value": "APPROVE" }, "breakdown": [ { "sub_check": "liveness_auth", "result": "PASS" } ] } }, { "type": "ID_DOCUMENT_FACE_MATCH", "id": "a0818baa-4641-4f34-89c8-8557805219cd", "state": "DONE", "report": { "recommendation": { "value": "APPROVE" }, "breakdown": [ { "sub_check": "ai_face_match", "result": "PASS", "details": [ { "name": "confidence_score", "value": "0.95" } ] } ] } } ], "user_tracking_id": "12345" }

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.