Sumsub — Create Level
Builds an
JSON payload from a compact spec, POSTs (or PATCHes) it to the Sumsub API, and reports the resulting
/
.
Endpoints
| Method | Path | When |
|---|
| /resources/applicants/-/levels
| Create a new level. Body must NOT include or — server assigns them. |
| /resources/applicants/-/levels
| Update an existing level (by in body). |
| /resources/applicants/-/levels/{id}
| Read one level. Use this to verify what landed (tenant gates may silently drop fields) or to resolve from a known . |
| /resources/applicants/-/levels
| List all levels (use to reuse existing levels before creating duplicates). |
Body:
. Returns the persisted level with
,
, audit trails.
Auth — App Token + secret (sandbox only)
This skill talks to the public Sumsub API and signs each request per
the authentication reference.
The full how-it-works writeup lives in the
skill — read it if you hit
.
⚠️ Sandbox tokens only. Do
not accept or use a production App Token
here — creating a level is a workspace-visible write. If the user offers a
prod token, refuse and ask them to generate a sandbox pair at
https://cockpit.sumsub.com/checkus/devSpace/appTokens (toggle the
workspace to
Sandbox first, then
Create). Token + secret are
shown once — copy both before closing the dialog. The helper script
enforces this — it rejects tokens that don't start with
.
| Var | Example |
|---|
| — sandbox App Token from the dashboard. |
| The paired secret shown once at token creation. |
| Optional. Defaults to . |
If the user has already supplied credentials in conversation, reuse them;
otherwise ask once before running. Never echo the secret back.
Procedure
-
Fetch tenant entitlements. Invoke the
skill and parse the JSON result. Store the
array; use it for all feature-gate checks in step 2 and the entitlements section below. Do this before anything else.
-
Check existing entities. Before building anything, list existing levels, POA presets, and questionnaires (GET the respective list endpoints). If a matching entity already exists, offer to reuse its
and skip the POST — the server creates a duplicate on every POST with no name deduplication.
-
Translate the user's request to the compact spec below — name, applicant type, and a list of doc-set steps in the order they should appear in the WebSDK flow.
-
Validate — confirm every
is a real
, every
step has a
that points to an existing questionnaire, every
step has a
/
(the server rejects the level if it's missing — run
first if no preset exists), every
step has at least one named sub-step. Use the
array from step 0 to reject any entitlement-gated feature not present in it.
-
Generate payload — run
${CLAUDE_SKILL_DIR}/scripts/build_level.py
with the spec on stdin → full payload on stdout.
-
Show the resolved payload to the user and ask for explicit confirmation before the first POST.
-
Create vs. update:
- New level — POST via
${CLAUDE_SKILL_DIR}/scripts/post_level.sh
. Returns response body + HTTP status.
- Update existing — GET the current state via
${CLAUDE_SKILL_DIR}/scripts/get_level.sh
so the user sees the diff, then PATCH via ${CLAUDE_SKILL_DIR}/scripts/patch_level.sh
. The spec passed to must include — the builder preserves it into the payload, and PATCH refuses bodies without it. PATCH replaces as a single array — every docSet you send must carry the full intended state, since fields omitted from a docSet are wiped on the server. Copy preserved values from the GET response into your PATCH spec.
-
GET the level back via
${CLAUDE_SKILL_DIR}/scripts/get_level.sh
and compare to what was sent — several fields land differently from what was sent (see gotchas in
references/level-schema.md). Report any discrepancy to the user.
-
Build the dashboard link. Read
,
, and
from the response body and format:
https://cockpit.sumsub.com/checkus/sdkIntegrations/levels/<applicantType>Level/<id>?clientId=<clientId>&xSNSEnv=sbx
The
segment is literally
for
applicantType: individual
(confirmed) and
for
(assumed by analogy — surface as the best guess and flag if it 404s). The
query param targets the
Sandbox workspace — it is the canonical sandbox link param shared across all skills.
-
Report — lead with the human-readable name:
- , , ordered list of docSets created.
- Dashboard link as a clickable markdown link.
- Final line:
Level ID (for SDK access tokens / future PATCH): <id>
.
Surface 4xx errors verbatim — they usually point to a missing
or an unknown enum value.
Tenant entitlements
Many level settings are gated behind tenant entitlements (
). Before enabling a feature, check that the required
is present in the
array returned by
(fetched in step 0).
DocSet type → required permission (OR — any one suffices):
| DocSet type | Required (any one of) |
|---|
| / | |
| / | | | KYB_AUTO_AML_AND_REGISTRY
| |
| / | |
| | | | KYT_UNHOSTED_WALLET_VERIFICATION
|
| |
| / | | ADVANCED_POA_TYPE_DETECTION
(often missing from even when the API actually allows it — proceed with a one-line warning; see below) |
| |
| |
| |
| |
Types not in this table (
,
,
,
,
, etc.) are available to all tenants — no entitlement required.
If a requested feature requires an entitlement the tenant doesn't have — stop immediately. Do not build or POST the level. Tell the user which entitlement is missing, that the feature is unavailable on their account, and that they need to contact their CSM or Sumsub support to get it enabled. Resume only after the user confirms the entitlement has been added or explicitly decides to drop the feature.
Exception — POA. The
/
ADVANCED_POA_TYPE_DETECTION
keys are often absent from
even on tenants where the API actually accepts
levels (the entitlement seems to be baseline or covered by other keys; the documented mapping is stale on some tenants). When only
is missing,
proceed with a one-line warning to the user so they have context if support is later needed. Do NOT pause for explicit confirmation — Sumsub itself will reject the write if the tenant truly lacks the right, and that 4xx will be more informative than a pre-emptive halt. Surface any entitlement-related error verbatim if it comes back.
Safety
Creating a level is a write to a shared workspace. Always:
- Show the resolved payload to the user before the first POST (step 4 above).
- After each successful POST, GET the entity back and compare to what was sent — silent overrides are common.
- Do not re-POST a dependency (PoA preset, questionnaire) if it already succeeded mid-session — reuse the returned .
- Do not delete or modify levels you didn't create in this session unless the user explicitly names them.
Names, not ids, in user-facing messages
This applies to every message you send the user about this level — not just the final report:
- Pre-POST summary: when you list which questionnaire and which POA preset will be attached, refer to each by / (e.g. "POA preset «POA — 60 days»", "questionnaire «Applicant basics»"). Do not paste the raw ("") into prose — the user can't read it and it does not let them judge whether you picked the right entity.
- Final report: still ends with
Level ID (for SDK access tokens / future PATCH): <id>
on its own line — that line is the one place a raw id is correct, because the user needs to copy it for the next API call.
- Diagnostic messages: when a 4xx response references a dependency id (POA preset not found, etc.), translate it to the name before showing the user.
If you don't yet know an entity's name (e.g. user supplied only an id from outside this session), GET the entity first and surface its name; do not fall back to the id.
Compact spec format
Accepts JSON or YAML on stdin. The builder fills in sensible defaults for each doc-set type (see references/level-schema.md).
json
{
"name": "Basic KYC",
"applicantType": "individual",
"type": "standalone",
"docSets": [
{"type": "IDENTITY", "docTypes": ["PASSPORT","ID_CARD","DRIVERS"]},
{"type": "SELFIE", "videoRequired": "passiveLiveness"},
{"type": "PROOF_OF_RESIDENCE", "docTypes": ["UTILITY_BILL"]},
{"type": "QUESTIONNAIRE", "questionnaireDefId": "source-of-funds"}
]
}
Supported values
,
,
,
,
/
/
,
,
,
,
,
,
,
,
,
,
,
,
,
,
/
/
/
,
,
,
,
.
Per-type compact shortcuts
| Shortcut keys | Builder expands to |
|---|
| ; ( / ); & (sent only when docapture); nfcVerificationSettings: {mode}
| flat fields on the docSet — only what you set. See Dashboard ↔ API mapping below. |
| (default ; full set: / / / / ), (default ); selfieProcessingSettings: {skipLivenessCheck, skipFaceMatchCheck}
(used with payment-method verification — see below) | bare docSet with [+ ] |
| (default ); (or ) to attach a POA preset by id | docSet + |
| (or alias) — required | bare docSet |
| — array of strings or {name, required, prefill, immutableIfPresent}
| with defaults |
| (object — keys: , , , — see Payment method verification below); (bool, default ); (bool); (enum — see schema) | with validated structure; always emits types: ["PAYMENT_SOURCE"]
|
| / | — | bare docSet |
| — array of {name, minDocsCnt?, idDocTypes?, idDocSubTypes?, fields?, applicantLevelName?}
| full KYB step structure |
| (pass-through) | as-is |
Unknown keys in a docSet are passed through to the API verbatim, so escape hatches are easy when you need an obscure field.
Dashboard ↔ API mapping for IDENTITY step
Verbatim labels from the Sumsub dashboard sidebar, paired with the API value to write. Match the user's description to a label, then use the value.
| Dashboard control | Label (user-visible) | API value | Spec key |
|---|
| Capture method | File upload | | |
| Capture method | Live capture | | |
| Capture mode¹ | Both manual and auto capture work at the same time | | |
| Capture mode¹ | Only manual capture is active | | |
| Capture mode¹ | Seamless live capture | | |
| Fallback to file upload¹ | Always available | | |
| Fallback to file upload¹ | Available only if camera capture failed | | |
| Fallback to file upload¹ | Not available | | |
| NFC verification² | Disabled | | nfcVerificationSettings.mode
|
| NFC verification² | Optional | | nfcVerificationSettings.mode
|
| NFC verification² | Required | | nfcVerificationSettings.mode
|
¹ Only meaningful with
. Silently dropped from the payload otherwise (matches the dashboard's own behavior — it deletes both keys when the radio is toggled to File upload).
²
auto-rejects Web SDK applicants; Mobile SDK only.
Defaults emitted by the builder (necessary for the dashboard to render — the controls bind to actual stored values, not implicit UI defaults; empty fields render as "Select" placeholder, not as the default option):
- →
captureMode: manualAndAuto
, (unless caller overrides).
- IDENTITY always gets
nfcVerificationSettings: {mode: disabled}
unless caller overrides.
These match what the dashboard's own Vue watcher writes when the user first toggles Live capture.
Caveat on PATCH: is replaced wholesale (only top-level Level fields merge) — every docSet in the PATCH spec must carry the full intended state, since omitted sub-fields are wiped. GET the level first and copy values you want to preserve.
Payment method verification (step or action)
Payment method verification — the
docSet, for verifying a bank card, bank account, crypto wallet, or e-wallet — runs in
two modes:
- As a step in a normal verification level: add a docSet alongside / / / etc. The level stays a regular level with no . Use this when payment verification is one part of a broader onboarding flow.
- As an action on an actions-type level: set and
actionType: "paymentMethod"
. Use this for a standalone, re-runnable payment-method check decoupled from onboarding.
Pick the mode from how the user frames it — "add card/wallet verification to my KYC level" → step; "create a standalone payment-method check / action" → action.
Step mode — inside a standard level (identity and payment coexist):
json
{
"name": "KYC + payment method",
"type": "standalone",
"applicantType": "individual",
"docSets": [
{"type": "IDENTITY"},
{"type": "SELFIE", "videoRequired": "passiveLiveness"},
{"type": "PAYMENT_METHODS",
"typeSettings": {"bankCard": {"allowed": true, "countImages": "one"}}}
]
}
Action mode — inside an actions level:
json
{
"name": "Payment method check",
"type": "actions",
"actionType": "paymentMethod",
"websdkNext": true,
"docSets": [
{"type": "PAYMENT_METHODS", "skipOwnershipCheck": false,
"typeSettings": {"bankCard": {"allowed": true, "countImages": "one"}}}
]
}
Constraints the builder enforces:
actionType: "paymentMethod"
requires , and the level must include a docSet.
- Cannot set both and simultaneously.
- goes at the level, not inside
typeSettings.cryptoWallet
(the builder places it correctly).
- needs a concrete verification method. Unlike , enabling a bank account with alone is rejected by the API — it must enable at least one of (statement upload, no extra entitlement) or
allowExternalSourcesCheck
(external data-source check, uses the entitlement). Ask the user which method(s) they want; don't offer a bare "standard" bank-account option.
The docSet (both modes): the builder always emits
types: ["PAYMENT_SOURCE"]
plus a
object assembled from your compact spec — the API rejects the docSet without
, so the builder never omits it.
| Key | Fields |
|---|
| (bool); ( / / ); (bool); extractNationalBankAccountNumbers
(bool); (bool) |
| (bool); (bool); allowExternalSourcesCheck
(bool — requires entitlement). When , at least one of these two methods must be — see constraints above. |
| (bool); (bool); ( / / / / / / ) |
| (bool) |
Entitlement: requires
or
KYT_UNHOSTED_WALLET_VERIFICATION
(see
Tenant entitlements).
Dashboard link: both modes live under
sdkIntegrations/levels/individualLevel/<id>
— the standard standalone-level URL pattern.
See
examples/payment-methods.json
for a complete action-mode spec.
Chaining with other Sumsub-* skills
This skill's
and
doc-sets accept ids produced by sibling skills, so a 3-step build-everything-from-scratch flow is natural:
| First run … | Pass the returned as … |
|---|
sumsub-create-questionnaire
| docSets[].questionnaireDefId
on a doc-set |
| on a doc-set |
See
examples/with-presets.json
for an end-to-end level that references both.
Outputs
On success, lead with the human-readable info:
- , , ordered list of .
- Dashboard link:
https://cockpit.sumsub.com/checkus/sdkIntegrations/levels/<applicantType>Level/<id>?clientId=<clientId>&xSNSEnv=sbx
. Render as a clickable markdown link. The segment is for individuals (confirmed) or for companies (assumed pattern). All three fields (, , ) come from the POST response body; targets the Sandbox workspace.
- Finally, on its own line:
Level ID (for SDK access tokens / future PATCH): <id>
.
On failure: HTTP status + the
/
from Sumsub's error envelope.
Worked examples
examples/identity-selfie.json
— the most common pattern.
examples/identity-live-capture.json
— IDENTITY-only with Live capture + all docapture sub-options + NFC.
examples/questionnaire-only.json
— pure data-collection level (like the SOF level in this repo).
- —
APPLICANT_DATA + IDENTITY + SELFIE + PROOF_OF_RESIDENCE + QUESTIONNAIRE
.
examples/kyb-company.json
— KYB level with company + UBOs + representatives steps.
examples/with-presets.json
— references both a questionnaire () and a POA preset () returned by sibling skills.
examples/payment-methods.json
— payment method action level with bank card, bank account, crypto wallet, and e-wallet.
See also
- references/level-schema.md — full schema, all enum values, gotchas.