> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pods.finance/llms.txt
> Use this file to discover all available pages before exploring further.

# KYC sandbox example

> Runnable Node.js examples for shared-token and hosted KYC sandbox flows

These two Node.js examples exercise the [KYC sandbox](https://docs.pods.finance/guides/kyc/sandbox.md). The shared-token script submits an applicant and token. The hosted script creates a session and receives a dummy verification link. Both explicitly move the applicant to a simulated result, then read it back.

The status GET is read-only in both flows. The scripts check that polling does not advance the profile. Applicant email does not select the result.

## 1. Shared token

The [shared-token script](https://docs.pods.finance/examples/javascript/kyc-sandbox.js) submits a Sumsub applicant and share token to Pods. With `SCENARIO=rejected_retryable`, it resubmits the same applicant with a fresh token and sets the second attempt to `approved`.

### Requirements

* Node.js 18 or newer, with built-in `fetch`.
* Your staging Pods API URL and staging customer API key. Send the key from your backend as `x-api-key`.
* Optional: Sumsub sandbox credentials, a level name, and the Pods recipient ID. If all four `SUMSUB_*` values are omitted, the script uses unique placeholder applicant IDs and tokens. Pods requires these fields but does not inspect their contents in the sandbox.

```bash theme={null}
export PODS_API_BASE_URL=https://staging-api.deframe.io
export PODS_API_KEY=your-staging-api-key
export SCENARIO=rejected_retryable

# Optional: set a unique wallet for this applicant. Otherwise the script generates one.
# export WALLET_ADDRESS=0x0000000000000000000000000000000000000001

# Optional: set all four to exercise your real Sumsub sandbox token generation.
export SUMSUB_APP_TOKEN=sbx:...
export SUMSUB_SECRET_KEY=...
export SUMSUB_LEVEL_NAME=...
export SUMSUB_FOR_CLIENT_ID=...
```

Download or copy [`kyc-sandbox.js`](https://docs.pods.finance/examples/javascript/kyc-sandbox.js), then run:

```bash theme={null}
node kyc-sandbox.js
```

`SCENARIO` accepts `approved`, `rejected`, or `rejected_retryable`. The default is `approved`. Each run generates a new valid CPF, applicant ID, and wallet unless you provide `WALLET_ADDRESS`, so previous test results do not affect it. If you save the script outside a project configured for ES modules, save it as `kyc-sandbox.mjs` and run `node kyc-sandbox.mjs`.

### How the example calls Pods

The script submits the normal production request shape. The sandbox returns a `kycUserId` in `provider_pending`:

```javascript theme={null}
const submission = await pods('POST', '/api/v1/kyc/sumsub-share-token', {
  cpf, email, walletAddress: WALLET, sumsubApplicantId: applicantId, shareToken
})
```

The submission is already pending, but the script also calls the optional pending route. This returns the current status without starting another attempt:

```javascript theme={null}
await pods('POST', '/api/v1/kyc/status/sandbox/provider_pending', {
  kycUserId: submission.kycUserId
})
```

It reads the status before setting an outcome and asserts that it is still `provider_pending`:

```javascript theme={null}
const pending = await pods('GET', `/api/v1/kyc/status?kycUserId=${submission.kycUserId}`)
if (pending.status !== 'provider_pending') throw new Error('Polling changed the status')
```

Next it calls the route for the chosen outcome. Every sandbox transition takes `{ "kycUserId": "…" }` and returns the full KYC status response:

```javascript theme={null}
const result = await pods(
  'POST',
  `/api/v1/kyc/status/sandbox/${SCENARIO}`,
  { kycUserId: submission.kycUserId }
)
```

A subsequent GET reads the same status. For `rejected_retryable`, the script requests a **new token for the same Sumsub applicant** (or makes a new placeholder token), resubmits the same CPF, email, wallet, and applicant ID, then explicitly sets `approved`. It checks that the retry keeps the original `kycUserId` and returns to `provider_pending` first.

```text theme={null}
Submit: provider_pending <kycUserId>
Explicit pending: provider_pending
Read before transition: provider_pending
Set outcome: rejected_retryable
Read after transition: rejected_retryable { code: 'VERIFICATION_RETRY_REQUIRED', ... }
Resubmit fresh token: provider_pending
Read after retry: approved
```

The full script signs Sumsub sandbox requests when credentials are supplied. It creates an applicant, simulates a GREEN review, and mints its first share token. On retry it mints a second token **for that existing applicant**, which is necessary to keep the Pods profile binding intact.

> [!WARNING]
>
> The transition routes exist only outside production. Sandbox outcomes simulate responses; no real Sumsub or Avenia verification occurs inside Pods. Keep both Pods and Sumsub credentials on your backend.

## 2. Hosted

The [hosted script](https://docs.pods.finance/examples/javascript/kyc-hosted-sandbox.js) creates a Pods-hosted session. It checks that the returned link is clearly dummy, reads `created` twice, starts the simulated review, sets an outcome, and reads the result. With `SCENARIO=rejected_retryable`, it calls the hosted reset endpoint and then approves a second attempt.

### Requirements

* Node.js 18 or newer, with built-in `fetch`.
* Your staging Pods API URL and staging customer API key. Pods Hosted KYC must be enabled for that customer.
* A redirect URL configured on the customer. Set `REDIRECT_URL` only when you want to override the customer default with another allowlisted URL.

No Sumsub credentials, applicant ID, or share token are needed for this flow. The script creates a fresh valid CPF, email, and wallet for each run unless you provide `WALLET_ADDRESS`.

```bash theme={null}
export PODS_API_BASE_URL=https://staging-api.deframe.io
export PODS_API_KEY=your-staging-api-key
export SCENARIO=rejected_retryable

# Optional: override the customer default with an allowlisted callback.
# export REDIRECT_URL=https://app.example.com/kyc/return
```

Save [`kyc-hosted-sandbox.js`](https://docs.pods.finance/examples/javascript/kyc-hosted-sandbox.js) as `kyc-hosted-sandbox.mjs` outside an ES-module project, then run:

```bash theme={null}
node kyc-hosted-sandbox.mjs
```

`SCENARIO` accepts `approved`, `rejected`, or `rejected_retryable`; its default is `approved`.

### How the example calls Pods

First, the script creates a hosted session. The sandbox response has `status: "created"` and a `verificationLink` under `.invalid`. The link is a placeholder and cannot collect documents or change status:

```javascript theme={null}
const session = await pods('POST', '/api/v1/kyc/hosted/sessions', {
  cpf,
  email,
  walletAddress: WALLET
})
```

After two GET calls confirm the profile is still `created`, the script starts the simulated review:

```javascript theme={null}
await pods('POST', '/api/v1/kyc/status/sandbox/provider_pending', {
  kycUserId: session.kycUserId
})
```

It reads `provider_pending` without changing it, then selects the outcome:

```javascript theme={null}
await pods('POST', `/api/v1/kyc/status/sandbox/${SCENARIO}`, {
  kycUserId: session.kycUserId
})
```

For a retryable result, the script calls the **hosted** reset route. It keeps the same `kycUserId`, returns to `created`, and then explicitly transitions to `provider_pending` and `approved`:

```javascript theme={null}
await pods('POST', '/api/v1/kyc/hosted/reset', {
  kycUserId: session.kycUserId
})
```

```text theme={null}
Create hosted session: created <kycUserId>
Read twice: created
Start review: provider_pending
Set outcome: rejected_retryable { code: 'VERIFICATION_RETRY_REQUIRED', ... }
Reset hosted session: created
Read after retry: approved
```

The hosted reset applies only to an eligible Sumsub-side `rejected_retryable` profile. It cannot restart a retryable rejection after an Avenia account link exists. See the [hosted sandbox steps](https://docs.pods.finance/guides/kyc/sandbox.md#2-hosted) for the guard and state rules.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.