sumsub-create-workflow

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Sumsub — Create Workflow

Sumsub — 创建工作流

Builds an
ApplicantWorkflow
JSON payload from a compact node/edge spec, validates it (dry-run), POSTs it as a
draft
, and then — gated by the workflow's current live exposure — publishes it via
PUT /{id}/revisionStatus
: directly when exposure is low, or only after explicit confirmation when it's high. Reports the resulting
name
(kind) /
revision
/
revisionStatus
/
id
. Read the Danger section before publishing anything.
从紧凑的节点/边缘规范构建
ApplicantWorkflow
JSON负载,对其进行验证(试运行),将其作为
draft
提交(POST请求),然后根据工作流的当前实时曝光量,通过
PUT /{id}/revisionStatus
发布:曝光量低时直接发布,曝光量高时仅在明确确认后发布。返回生成的
name
(类型)/
revision
(修订版本)/
revisionStatus
(修订状态)/
id
。发布任何内容前请阅读“危险”部分。

Concepts

概念

A workflow routes applicants through levels and branches. There is exactly one workflow per kind:
default
(auto-runs for every verification),
actions
(auto-runs for applicant actions),
test
(isolated; runs only from its draft, launched manually per applicant — not publishable). Each save creates a new revision of that singleton, never a new workflow. A revision is a draft — editable, routes no traffic, always safe to create / update / validate — until it is published, which makes it live for all real production traffic and auto-archives the previously published revision.
test
never publishes; it runs from its draft.
工作流将申请人引导通过不同层级和分支。每种类型(kind)对应恰好一个工作流:
  • default
    (默认):对每一次验证自动运行
  • actions
    (操作):对申请人操作自动运行
  • test
    (测试):独立运行;仅从草稿手动针对单个申请人启动——不可发布
每次保存都会创建该单一实例的新修订版本(revision),而非新工作流。修订版本最初是草稿(draft)——可编辑,不路由流量,创建/更新/验证操作始终安全——直到被发布(published),发布后它会处理所有实际生产流量,并自动归档之前已发布的修订版本。
test
类型永远不会发布;它从草稿直接运行。

Endpoints

端点

MethodPathWhen
POST
/resources/api/agent/applicantWorkflows
Create (no
id
) or update a draft by
id
. Only drafts are editable; a
published
/
archived
revisionStatus
here is rejected. Always safe — drafts route no traffic.
POST
/resources/api/agent/applicantWorkflows/-/validate
Dry-run validation. Returns
notices[]
without persisting. Read-only.
GET
/resources/api/agent/applicantWorkflows
List workflows (every revision, all statuses). Response is flat:
{ "items": [...], "totalItems": N, "pageInfo": { "limit", "offset" }, "error": {...}? }
—
items
is top-level (no
list
wrapper).
GET
/resources/api/agent/applicantWorkflows/{id}
Read one revision (the
id
is a specific revision, not the workflow name).
GET
/resources/api/agent/applicantWorkflows/{id}/traffic
Applicants recently on this revision's levels:
{"applicantCount": N}
. Read-only; the input to the publish exposure gate. Level-derived, not workflow-routed — see Danger.
POST
/resources/api/agent/applicantWorkflows/{id}/draft
Fork a new draft from a published/archived revision — the way to iterate on a live workflow without touching it.
PUT
/resources/api/agent/applicantWorkflows/{id}/revisionStatus
Publish / archive a revision. Body
{"revisionStatus":"published"|"archived"|"draft"}
. Publishing requires zero
error
notices and auto-archives the prior published revision. ⚠️ Affects live traffic — see Danger.
Permission required on the App Token:
manageWorkflows
. Sandbox tokens typically have it; if you hit
403 Unauthorized (atd)
, regenerate the token with the right scopes.
方法路径适用场景
POST
/resources/api/agent/applicantWorkflows
创建(无
id
)或按
id
更新草稿。仅草稿可编辑;若此处传入
published
/
archived
状态的
revisionStatus
会被拒绝。始终安全——草稿不路由流量。
POST
/resources/api/agent/applicantWorkflows/-/validate
试运行验证。返回
notices[]
但不持久化数据。只读操作。
GET
/resources/api/agent/applicantWorkflows
列出所有工作流(所有修订版本、所有状态)。响应为扁平化结构:
{ "items": [...], "totalItems": N, "pageInfo": { "limit", "offset" }, "error": {...}? }
——
items
为顶层字段(无
list
包装器)。
GET
/resources/api/agent/applicantWorkflows/{id}
读取单个修订版本(
id
对应特定修订版本,而非工作流名称)。
GET
/resources/api/agent/applicantWorkflows/{id}/traffic
最近在此修订版本层级上的申请人数量:
{"applicantCount": N}
。只读操作;是发布曝光管控的输入依据。基于层级统计,而非工作流路由——请查看“危险”部分。
POST
/resources/api/agent/applicantWorkflows/{id}/draft
从已发布/已归档的修订版本分叉出新草稿——这是在不影响实时工作流的前提下迭代的方式。
PUT
/resources/api/agent/applicantWorkflows/{id}/revisionStatus
发布/归档修订版本。请求体为
{"revisionStatus":"published"|"archived"|"draft"}
。发布要求无
error
通知,且会自动归档之前已发布的修订版本。⚠️ 影响实时流量——请查看“危险”部分。
应用令牌需具备权限:
manageWorkflows
。沙箱令牌通常已包含该权限;若遇到
403 Unauthorized (atd)
错误,请重新生成带有正确权限范围的令牌。

⚠️ Danger — publishing affects LIVE production traffic

⚠️ 危险——发布操作影响实时生产流量

Workflow revisions are shared across live and sandbox; only applicants are isolated. Publishing changes the workflow that handles all real production traffic — a sandbox
sbx:
token does not make this safe. This is the single most important fact about this skill. (Drafts, by contrast, route no traffic — create / update / validate them freely.)
Publishing
default
/
actions
is gated by the workflow's current live exposure — the recent applicant traffic on the revision that is currently
published
(the one a publish auto-archives), measured by the
workflow_api.py traffic
subcommand. The threshold is 400 recent applicants (override:
SUMSUB_PUBLISH_TRAFFIC_THRESHOLD
):
Workflow
name
Current live exposurePublishing
default
/
actions
> 400 recent applicants, or unreadableFully guarded: clean validate · diff vs current published · explicit user "yes" ·
SUMSUB_ALLOW_WORKFLOW_PUBLISH=1
latch
default
/
actions
≤ 400, or no published revision yetLow-exposure: publish directly — no confirmation, no latch (still report what you did)
test
—Not publishable —
revisionStatus
is immutable (the API rejects the call); it runs from its draft
The exposure count is level-derived — it counts applicants recently seen on the level names the published revision contains (so revisions sharing level names report overlapping counts), reflecting how busy this workflow's levels are now, not the new revision's blast radius. Phrase it to the user as "the levels in this revision have N recent applicants," never "this workflow handled N." Rules that hold regardless of exposure:
  • Publishing is the default end of a build, not a separate request — the gate above decides what it costs (low → direct; high → confirm + latch). The only thing that keeps a build at a draft is the user explicitly opting out ("just save a draft").
  • Fail safe on unknown exposure. If the live revision's traffic can't be read (API error), treat it as over-threshold and run the full guarded flow.
  • Never switch kinds to dodge the gate. A publish request can't be made "safer" by retargeting to
    test
    (the API rejects it anyway). Publish the requested kind.
  • workflow_api.py publish
    is the backstop: it independently re-measures exposure and requires
    SUMSUB_ALLOW_WORKFLOW_PUBLISH=1
    only when exposure is over-threshold or unreadable — letting a low-exposure publish through unlatched.
工作流修订版本在实时环境和沙箱环境之间共享;仅申请人数据是隔离的。发布操作会更改处理所有实际生产流量的工作流——沙箱
sbx:
令牌无法保证操作安全。这是本技能最重要的一点。(相比之下,草稿不路由流量——可自由创建/更新/验证。)
发布
default
/
actions
类型工作流受限于工作流的当前实时曝光量——即当前处于
published
状态的修订版本(发布操作会自动归档该版本)的近期申请人流量,由
workflow_api.py traffic
子命令统计。阈值为400个近期申请人(可通过
SUMSUB_PUBLISH_TRAFFIC_THRESHOLD
覆盖):
工作流
name
当前实时曝光量发布规则
default
/
actions
> 400个近期申请人,或无法读取曝光量完全管控:验证通过 · 与当前已发布版本对比差异 · 用户明确确认“是” · 设置
SUMSUB_ALLOW_WORKFLOW_PUBLISH=1
开关
default
/
actions
≤ 400个,或尚无已发布版本低曝光:直接发布——无需确认,无需开关(仍需报告操作内容)
test
——不可发布——
revisionStatus
不可修改(API会拒绝请求);从草稿直接运行
曝光量计数是基于层级的——统计最近出现在已发布修订版本包含的层级名称上的申请人数量(因此共享层级名称的修订版本会报告重叠计数),反映的是当前该工作流层级的繁忙程度,而非新修订版本的影响范围。向用户表述时应说*“此修订版本中的层级近期有N个申请人”,而非“此工作流处理了N个申请人”*。无论曝光量如何,以下规则始终适用:
  • 发布是构建流程的默认终点,而非单独请求——上述管控规则决定发布的条件(低曝光→直接发布;高曝光→确认+开关)。仅当用户明确选择退出(“仅保存草稿”)时,构建流程才会停留在草稿阶段。
  • 曝光量未知时安全处理。若无法读取实时修订版本的流量(API错误),则视为超过阈值,执行完整的管控流程。
  • 切勿通过切换类型规避管控。无法通过将发布请求目标改为
    test
    来“降低风险”(API仍会拒绝)。请发布用户请求的类型。
  • workflow_api.py publish
    是最后保障:它会独立重新测量曝光量,仅当曝光量超过阈值或无法读取时,才要求设置
    SUMSUB_ALLOW_WORKFLOW_PUBLISH=1
    ——低曝光发布无需开关即可通过。

Auth — App Token + secret (sandbox only)

认证——应用令牌+密钥(仅沙箱环境)

Each request is signed with App Token + secret (HMAC-SHA256). Full signing details and
401
debugging:
sumsub-api-auth
.
⚠️ Sandbox only. A workflow routes real applicants, so production tokens are refused — the scripts reject anything not prefixed
sbx:
. Mint a sandbox pair in the dashboard (switch the workspace to Sandbox → App tokens → Create; both are shown once).
VarExample
SUMSUB_APP_TOKEN
sbx:...
— sandbox App Token from the dashboard.
SUMSUB_SECRET_KEY
The paired secret shown once at token creation.
SUMSUB_BASE
Optional. Defaults to
https://api.sumsub.com
.
If the user has already supplied credentials in conversation, reuse them; otherwise ask once before running. Never echo the secret back.
Not a credential: publishing a high-exposure
default
/
actions
revision (live traffic above the threshold, or unreadable) is armed by
SUMSUB_ALLOW_WORKFLOW_PUBLISH=1
, but that is not a configured env var — it is a per-command arming token you type inline on the publish call (step 8), nothing else. Never add it to
settings.local.json
or any persistent env: doing so leaves the gate permanently open, which defeats its only purpose. It belongs on exactly one command line, at the moment you publish, and nowhere else.
每个请求都使用应用令牌+密钥进行签名(HMAC-SHA256)。完整签名细节及
401
错误调试:
sumsub-api-auth
。
⚠️ 仅适用于沙箱环境。工作流会路由真实申请人,因此会拒绝生产环境令牌——脚本会拒绝所有非
sbx:
前缀的令牌。在控制面板中生成沙箱令牌对(切换工作区至
Sandbox
→应用令牌→创建;两者仅显示一次)。
变量示例
SUMSUB_APP_TOKEN
sbx:...
——来自控制面板的沙箱应用令牌。
SUMSUB_SECRET_KEY
创建令牌时显示的配对密钥,仅显示一次。
SUMSUB_BASE
可选。默认值为
https://api.sumsub.com
。
若用户已在对话中提供凭据,请复用;否则在运行前询问一次。切勿回显密钥。
注意:这不是凭据——发布高曝光的
default
/
actions
修订版本(实时流量超过阈值,或无法读取)需要设置
SUMSUB_ALLOW_WORKFLOW_PUBLISH=1
,但这不是配置环境变量——它是发布命令中手动输入的单次启用令牌(步骤8),仅此而已。切勿将其添加到
settings.local.json
或任何持久化环境中:这样会永久打开管控开关,失去其唯一作用。它仅应出现在发布时的命令行中,别无他处。

Principles

原则

These three govern every step below; the procedure points back here where each one bites. They share one spine — the user's intent and the workspace's real entities are the ground truth; your job is to wire them faithfully, never to invent or improve.
  1. Build exactly what the user specified. Their level names, node structure, branch logic, and chosen
    kind
    are fixed constraints — not raw material to improve. If your own reasoning concludes a choice is redundant or suboptimal — often resting on a wrong assumption about how levels or the engine behave (e.g. chained levels don't re-capture already-collected data — see references/workflow-patterns.md) — build it literally and flag the concern in your report, never silently substitute a design you prefer. Validation catches what's invalid; "suboptimal" is not "invalid," and is never license to change their design.
  2. Wire entities; don't own them. Every level, client list, and tag a node references must resolve to something real before you build on it — a name the user gave you, one you confirmed via
    GET /resources/applicants/-/levels
    , or one explicitly being created (by you, a companion skill, or the user). An ungrounded reference is a stop, not a blank to fill: reuse what exists, get it created, or mark it explicitly pending — never invent a plausible name to make the graph look complete (that a missing reference blocks only publish, not the draft, is no license to fabricate one). Ground proactively — read the existing levels and reuse a real one before asking or inventing.
  3. Aim at the reference, not the green checkmark. Structural/builder errors are yours to fix — iterate until clean. But a missing-entity error (
    levelNotFound
    ,
    tagDoesNotExist
    , unknown client list) is cleared only by grounding the reference — never by repointing it at a convenient entity to make validation pass. And report the territory, not the map you wish were true: a draft with unresolved references is unfinished, and structure you bent to fit an existing entity is a change to the user's policy — surface both plainly, never as a clean success.
以下三条原则指导后续每一步;流程中会在相关步骤回溯到这些原则。它们的核心是用户的意图和工作区的实际实体是唯一依据;你的任务是忠实地实现,而非自行创造或优化。
  1. 严格按照用户要求构建。用户指定的层级名称、节点结构、分支逻辑和选择的
    kind
    是固定约束——而非可优化的素材。若你的推理认为某个选择冗余或次优——通常是基于对层级或引擎行为的错误假设(例如,链式层级不会重新采集已收集的数据——请参阅references/workflow-patterns.md)——严格按照用户要求构建,并在报告中标记该问题,切勿擅自替换为你偏好的设计。验证仅能发现无效内容;“次优”不等于“无效”,绝不能以此为理由更改用户的设计。
  2. 关联现有实体,而非自行创建。节点引用的每个层级、客户列表和标签必须对应真实存在的实体——用户提供的名称、你通过
    GET /resources/applicants/-/levels
    确认的名称,或明确正在创建的实体(由你、配套技能或用户创建)。未关联的引用是终止信号,而非空白填充:复用现有实体、等待创建完成,或明确标记为待处理——切勿为了让图看起来完整而编造合理名称(即使缺失的引用仅阻止发布而非草稿创建,也不能成为编造的理由)。主动关联——在询问或创建之前,先读取现有层级并复用真实存在的层级。
  3. 以实际情况为基准,而非追求验证通过。结构/构建错误由你修复——迭代直至验证通过。但缺失实体错误(
    levelNotFound
    、
    tagDoesNotExist
    、未知客户列表)仅能通过关联真实实体解决——切勿将引用指向方便的实体以通过验证。并且报告实际情况,而非你期望的情况:存在未解决引用的草稿是未完成的,为适配现有实体而修改的结构是对用户策略的更改——请明确指出这两点,切勿伪装成完美成功。

Procedure

流程

  1. Resolve the workflow
    kind
    from the user's own words, and state it back.
    If the user named no kind, it is
    default
    (the spec default). Use
    test
    or
    actions
    only when the user explicitly names them. Never infer, default to, or substitute a kind the user didn't ask for. State the kind as an assumption and proceed — say e.g. "treating this as
    default
    since you didn't specify a kind"
    so it's visible and correctable; don't turn the kind into a multiple-choice question. Never offer
    test
    as a "safe place to build" a
    default
    workflow
    to avoid touching an existing draft:
    test
    is for manual, per-applicant test runs, has its own separate draft, cannot be published, and has no promotion path into
    default
    . An existing
    default
    draft is never a reason to build elsewhere — just overwrite it (step 3).
  2. Translate the user's flowchart into the compact spec (below). One node per box / decision diamond, one edge per arrow. Use
    id
    s that read like labels (
    country-check
    ,
    route
    ,
    idv
    ,
    reject-region
    ). If the user gives a policy rather than a flowchart ("build me a KYC/KYB workflow"), don't start from a blank page — decompose it with the canonical pipeline spine and primitives in references/workflow-patterns.md, then stamp that spine per segment. Build only what the user specified, and ground every level / list / tag reference before building on it (Principles 1–2).
  3. Validate the graph: every edge
    from
    /
    to
    resolves to a node id; every
    applicantLevel
    /
    actionApplicantLevel
    references a real level by name; every
    exclusiveChoice
    has ≥1 branching out-edge (
    condition:
    or
    on:
    ); final-rejection nodes carry
    labels
    or
    buttonIds
    . (A level — and every non-choice node — has a single, unconditional out-edge; only an
    exclusiveChoice
    may branch — see Edge
    on:
    .) The graph is a directed acyclic forest: edges point only forward (no cycles/back-edges — document resubmission is not a back-edge; the engine retries in place), while fan-in and shared downstream nodes are fine and several entry levels may root independent flows. Never bridge different applicant types (KYC individual vs KYB company) with an edge — an applicant's type can't change. Full graph rules: Constraints in references/workflow-schema.md; resubmission in references/workflow-patterns.md.
  4. Check current state with
    ${CLAUDE_SKILL_DIR}/scripts/workflow_api.py state <name>
    for the target workflow
    name
    , then pick the write mode: no revisions → create (POST without
    id
    ); a draft already exists → update it in place (POST with that
    id
    ; a no-
    id
    POST would
    409
    ); a published revision but no draft → seed a draft with
    ${CLAUDE_SKILL_DIR}/scripts/workflow_api.py fork <id>
    , or just POST a fresh one. Overwriting an existing draft is the normal, expected operation — do it silently. The draft is a scratch surface the skill evolves across multiple runs, so don't ask before overwriting and don't back it up by default. Only if the user explicitly asks to preserve their current draft, first read it with
    ${CLAUDE_SKILL_DIR}/scripts/workflow_api.py get <id>
    and save the JSON body to a file (the command appends a trailing
    HTTP <code>
    line — keep only the JSON), then overwrite. When editing, preserve existing node and edge ids —
    get <id>
    the revision you're basing on and carry its ids through, minting new ids only for new nodes/edges (the engine matches in-flight runs on those ids; rationale in Revision continuity, references/workflow-schema.md).
  5. Generate payload with
    ${CLAUDE_SKILL_DIR}/scripts/build_workflow.py
    — spec on stdin → full payload on stdout. The builder assembles the payload from the real node types and edge
    condition
    ASTs, and validates node types, operators, and action
    targetType
    against its built-in known sets — so typos and unsupported constructs error here, before any network call (the API's validate is authoritative for the rest — levels, client lists, labels). Set the draft's
    id
    in the spec when updating in place (step 3).
  6. Validate (dry-run, stores nothing). Pipe the payload to
    ${CLAUDE_SKILL_DIR}/scripts/workflow_api.py validate
    →
    notices[]
    .
    info
    = advisory;
    warning
    = non-blocking but usually a real bug;
    error
    = publish-blocker. Look up any notice's meaning, severity, params, and how to act in references/workflow-notices.md. Clear a missing-entity error by grounding the reference, not by repointing it to pass validation (Principle 3). Always validate before persisting.
  7. POST via
    ${CLAUDE_SKILL_DIR}/scripts/workflow_api.py post
    — create (no
    id
    ) or update in place (with the
    id
    from step 3). Response carries the persisted draft with
    id
    ,
    clientId
    ,
    notices[]
    .
  8. Build the dashboard link from
    clientId
    in the response body:
    https://cockpit.sumsub.com/checkus/sdkIntegrations/workflows/active/applicant?clientId=<clientId>&sbx=true
    sbx=true
    targets the Sandbox workspace. ⚠️ Not a per-
    id
    deep link — it shows the current draft; tell the user to click promptly.
  9. Publish — the default end of a build, gated by exposure. Skip only if the user opted out ("just save a draft") — a draft routes no traffic, so it's not a useful end state on its own. The draft must have last validated clean (step 5) — the API refuses to publish a revision with any
    error
    notice. Run
    ${CLAUDE_SKILL_DIR}/scripts/workflow_api.py publish <draft-id>
    ; it owns the live-exposure gate (defined in Danger) and does one of two things:
    • Low exposure → it publishes directly; you're done.
    • High exposure → it refuses before changing anything, printing the exposure count and the currently-published revision's
      id
      (your one-command rollback point). To proceed, run the guarded flow: diff the draft against that published revision, disclose the change and that count to the user ("makes revision N live for ALL real production traffic; the current published revision — whose levels have ~M recent applicants — will be auto-archived"), get an explicit "yes", then re-run prefixed with
      SUMSUB_ALLOW_WORKFLOW_PUBLISH=1
      .
    Rollback: re-publish the previously-published
    id
    —
    SUMSUB_ALLOW_WORKFLOW_PUBLISH=1 ${CLAUDE_SKILL_DIR}/scripts/workflow_api.py publish <previous-published-id>
    (keep the latch — once it's live again, the revision you're replacing may itself be over-threshold).
    test
    is never published — see Danger.
  10. Report (lead with the human-readable identity) — and report the territory, not the map you wish were true: flag any unresolved reference or bent structure plainly, never as a clean success (Principle 3). Lead with the workflow
    name
    (
    default
    /
    actions
    /
    test
    ) and
    revision
    — e.g. «default» revision 14 — then
    revisionStatus
    ; node/edge counts and the sorted unique level names referenced (so misspellings show); the dashboard link (step 7) as a clickable markdown link. If you published, say so, name the now-
    published
    revision, note the auto-archived previous revision, and give its id as the rollback point. End with a single
    Workflow ID: <id>
    line — the one place a raw id belongs. On failure, surface Sumsub's
    description
    /
    errorName
    verbatim (builder validation errors already print a precise message).
  1. 从用户表述中解析工作流
    kind
    ,并反馈给用户
    。若用户未指定类型,则默认为**
    default
    (规范默认值)。仅当用户明确指定时,才使用
    test
    或
    actions
    。切勿推断、默认或替换用户未要求的类型。将类型作为假设陈述并继续——例如,说*“由于你未指定类型,将此视为
    default
    类型”*,以便用户可见并纠正;不要将类型变成选择题。切勿将
    test
    作为
    default
    工作流的“安全构建场所”以避免修改现有草稿
    :
    test
    用于手动、针对单个申请人的测试运行,有自己独立的草稿,无法发布,且
    没有升级为
    default
    的路径**。现有
    default
    草稿绝不是在其他地方构建的理由——直接覆盖即可(步骤3)。
  2. 将用户的流程图转换为紧凑规范(如下所示)。每个框/决策菱形对应一个节点,每个箭头对应一条边缘。使用类似标签的
    id
    (如
    country-check
    、
    route
    、
    idv
    、
    reject-region
    )。若用户给出的是策略而非流程图(“帮我构建一个KYC/KYB工作流”),不要从零开始——使用references/workflow-patterns.md中的标准流程框架和原语分解策略,然后按段构建。仅构建用户指定的内容,并在构建前关联每个层级/列表/标签引用(原则1-2)。
  3. 验证图结构:每条边缘的
    from
    /
    to
    都对应有效的节点id;每个
    applicantLevel
    /
    actionApplicantLevel
    都按名称引用真实层级;每个
    exclusiveChoice
    至少有一条分支边缘(
    condition:
    或
    on:
    );最终拒绝节点带有
    labels
    或
    buttonIds
    。(层级——以及所有非选择节点——有唯一、无条件的输出边缘;仅
    exclusiveChoice
    可分支——请参阅边缘
    on:
    )。图是有向无环森林:边缘仅向前指向(无循环/反向边缘——文档重新提交不是反向边缘;引擎会在原地重试),而扇入和共享下游节点是允许的,多个入口层级可作为独立流程的根节点。切勿用边缘连接不同类型的申请人(KYC个人 vs KYB企业)——申请人类型无法更改。完整图规则:references/workflow-schema.md中的Constraints;重新提交规则在references/workflow-patterns.md中。
  4. 检查当前状态:使用
    ${CLAUDE_SKILL_DIR}/scripts/workflow_api.py state <name>
    查看目标工作流
    name
    的状态,然后选择写入模式:无修订版本→创建(无
    id
    的POST请求);已有草稿→原地更新(带该
    id
    的POST请求;无
    id
    的POST请求会返回
    409
    );有已发布修订版本但无草稿→使用
    ${CLAUDE_SKILL_DIR}/scripts/workflow_api.py fork <id>
    生成草稿,或直接提交新草稿。覆盖现有草稿是正常、预期的操作——无需告知用户。草稿是技能在多次运行中迭代的临时区域,因此覆盖前无需询问,默认无需备份。仅当用户明确要求保留当前草稿时,先使用
    ${CLAUDE_SKILL_DIR}/scripts/workflow_api.py get <id>
    读取草稿并将JSON主体保存到文件(命令末尾会追加
    HTTP <code>
    行——仅保留JSON部分),然后覆盖。编辑时,保留现有节点和边缘id——获取你基于的修订版本的
    get <id>
    结果并沿用其id,仅为新节点/边缘生成新id(引擎通过这些id匹配正在进行的运行;理由见references/workflow-schema.md中的Revision continuity)。
  5. 生成负载:使用
    ${CLAUDE_SKILL_DIR}/scripts/build_workflow.py
    ——标准输入传入规范→标准输出输出完整负载。构建器从真实节点类型和边缘
    condition
    AST组装负载,并根据内置已知集合验证节点类型、运算符和操作
    targetType
    ——因此拼写错误和不支持的结构会在此处报错,无需网络请求(API的验证对其余内容——层级、客户列表、标签——具有权威性)。更新时在规范中设置草稿的
    id
    (步骤3)。
  6. 验证(试运行,不存储数据)。将负载通过管道传递给
    ${CLAUDE_SKILL_DIR}/scripts/workflow_api.py validate
    →返回
    notices[]
    。
    info
    =建议;
    warning
    =非阻塞但通常是真实错误;
    error
    =发布阻止项。在references/workflow-notices.md中查找任何通知的含义、严重性、参数和处理方法。通过关联真实实体解决缺失实体错误,而非修改引用以通过验证(原则3)。始终在持久化前进行验证。
  7. 提交(POST):使用
    ${CLAUDE_SKILL_DIR}/scripts/workflow_api.py post
    ——创建(无
    id
    )或原地更新(带步骤3的
    id
    )。响应包含持久化的草稿,带有
    id
    、
    clientId
    、
    notices[]
    。
  8. 构建控制面板链接:从响应体中的
    clientId
    生成:
    https://cockpit.sumsub.com/checkus/sdkIntegrations/workflows/active/applicant?clientId=<clientId>&sbx=true
    sbx=true
    指向沙箱工作区。⚠️ 这不是针对单个
    id
    的深层链接——它显示当前草稿;请告知用户及时点击。
  9. 发布——构建流程的默认终点,受曝光量管控。仅当用户选择退出(“仅保存草稿”)时跳过——草稿不路由流量,因此本身不是有用的最终状态。草稿必须在最后一次验证中通过(步骤5)——API会拒绝发布带有任何
    error
    通知的修订版本。运行
    ${CLAUDE_SKILL_DIR}/scripts/workflow_api.py publish <draft-id>
    ;它负责实时曝光管控(定义见“危险”部分),并执行以下操作之一:
    • 低曝光→直接发布;流程完成。
    • 高曝光→在更改前拒绝,打印曝光量计数和当前已发布修订版本的
      id
      (你的一键回滚点)。要继续,执行管控流程:对比草稿与已发布版本的差异,向用户披露更改内容和曝光量计数(“将修订版本N投入所有实际生产流量使用;当前已发布的修订版本——其层级近期约有M个申请人——将被自动归档”),获取用户明确的“是”,然后在命令前添加
      SUMSUB_ALLOW_WORKFLOW_PUBLISH=1
      重新运行。
    回滚:重新发布之前已发布的
    id
    ——
    SUMSUB_ALLOW_WORKFLOW_PUBLISH=1 ${CLAUDE_SKILL_DIR}/scripts/workflow_api.py publish <previous-published-id>
    (保留开关——重新发布后,你替换的修订版本本身可能已超过阈值)。
    test
    类型永远不会发布——请查看“危险”部分。
  10. 报告(以人类可读的标识开头)——报告实际情况,而非你期望的情况:明确标记任何未解决的引用或修改的结构,切勿伪装成完美成功(原则3)。开头说明工作流
    name
    (
    default
    /
    actions
    /
    test
    )和
    revision
    ——例如,«default»修订版本14——然后是
    revisionStatus
    ;节点/边缘数量和引用的唯一层级名称排序(以便发现拼写错误);控制面板链接(步骤7)作为可点击的markdown链接。若已发布,请说明,指出当前已发布的修订版本,提及自动归档的之前版本,并给出其id作为回滚点。最后以单独一行
    Workflow ID: <id>
    结束——这是唯一适合显示原始id的地方。失败时,直接显示Sumsub的
    description
    /
    errorName
    (构建器验证错误已打印精确消息)。

Compact spec format (JSON or YAML on stdin)

紧凑规范格式(标准输入为JSON或YAML)

kind
selects which workflow you're editing
(see Concepts for what each kind means and how revisions/drafts work). It is set only by the user's explicit request (see step 0); the default is
default
. ⚠️
default
/
actions
intercept real traffic once published — see the Danger section. (The schema has a
title
field, but the engine ignores it — the skill neither sets nor relies on it.)
Nodes use the real schema node-type in
type
; edges carry the real Sumsub
condition
AST
directly (no expression mini-language).
on:
is a shorthand for
reviewDecisions
.
yaml
kind: default                   # optional — default | test | actions (the workflow to edit)
revisionStatus: draft           # optional; only `draft` on create. Publishing is a separate, gated step (see Danger).

nodes:
  - id: country-check
    type: applicantLevel
    levelName: "Country Selector"
  - id: route
    type: exclusiveChoice
    name: "Country router"
  - id: idv
    type: applicantLevel
    levelName: "IDV"
  - id: idv-result
    type: exclusiveChoice
    name: "IDV result"
  - id: reject-region
    type: finalRejection
    labels: ["WRONG_USER_REGION"]
  - id: manual
    type: manualReview

edges:
  # A level's out-edge is single & unconditional — branch only at an explicit exclusiveChoice.
  - { from: country-check, to: route }                  # plain: level → choice
  - from: route                                         # embargo first (any decision)
    to: reject-region
    condition: { or: [ { and: [ { op: in, args: [ {exp: "applicant.country"}, {lit: "[\"UZB\", \"DEU\"]"} ] } ] } ] }
  - from: route                                         # approved & allowed → IDV
    to: idv
    on: approved
    condition: { or: [ { and: [ { op: eq, args: [ {exp: "applicant.country"}, {lit: "\"USA\""} ] } ] } ] }
  - { from: idv, to: idv-result }                       # plain: level → choice
  - { from: idv-result, to: manual, on: rejected }      # resubmission unhandled → engine retries (the default)
kind
选择你要编辑的工作流
(请参阅“概念”部分了解每种类型的含义以及修订版本/草稿的工作方式)。它仅由用户的明确请求设置(请参阅步骤0);默认值为
default
。⚠️
default
/
actions
类型发布后会拦截真实流量——请查看“危险”部分。(规范中有
title
字段,但引擎会忽略它——本技能既不设置也不依赖该字段。)
节点在
type
中使用真实规范节点类型;边缘直接携带真实Sumsub
condition
AST
(无表达式迷你语言)。
on:
是
reviewDecisions
的简写。
yaml
kind: default                   # 可选——default | test | actions(要编辑的工作流类型)
revisionStatus: draft           # 可选;创建时仅支持`draft`。发布是单独的管控步骤(请参阅“危险”部分)。

nodes:
  - id: country-check
    type: applicantLevel
    levelName: "Country Selector"
  - id: route
    type: exclusiveChoice
    name: "Country router"
  - id: idv
    type: applicantLevel
    levelName: "IDV"
  - id: idv-result
    type: exclusiveChoice
    name: "IDV result"
  - id: reject-region
    type: finalRejection
    labels: ["WRONG_USER_REGION"]
  - id: manual
    type: manualReview

edges:
  # 层级的输出边缘是唯一且无条件的——仅在显式exclusiveChoice处分支。
  - { from: country-check, to: route }                  # 普通:层级→选择
  - from: route                                         # 先拦截禁运国家(任意决策)
    to: reject-region
    condition: { or: [ { and: [ { op: in, args: [ {exp: "applicant.country"}, {lit: "[\"UZB\", \"DEU\"]"} ] } ] } ] }
  - from: route                                         # 批准且允许→IDV
    to: idv
    on: approved
    condition: { or: [ { and: [ { op: eq, args: [ {exp: "applicant.country"}, {lit: "\"USA\""} ] } ] } ] }
  - { from: idv, to: idv-result }                       # 普通:层级→选择
  - { from: idv-result, to: manual, on: rejected }      # 未处理重新提交→引擎重试(默认行为)

Node types

节点类型

A node is
{ id, type, name?, <body> }
, where
type
is the real schema node type. Body fields may be flattened (shown below) or written in the real nested form (
applicantLevel: { levelName }
).
Standard verification workflow (
kind: default|test
):
type
Body
applicantLevel
levelName
(string); optional
disableGoBack
exclusiveChoice
— (branches via outgoing edges)
actions
actions:
list (see below)
manualReview
—
finalRejection
labels
OR
buttonIds
Action workflow (
kind: actions
only): each
action*
type mirrors its standard counterpart —
actionApplicantLevel
,
actionExclusiveChoice
,
actionActions
,
actionManualReview
,
actionFinalRejection
take the same body as the names without the
action
prefix. Plus one extra:
actionApplicantTransition
(
levelName
— the
default
-workflow level to hand the verification back into; it must exist in the published
default
workflow
, or validate raises
missingLevelsForTransition
).
action*
types are valid only inside an action workflow (a graph that runs after a primary verification — typically tagging / noting / changing source key, or transitioning the applicant back into
default
); standard types only in
default
/
test
. Mixing them is rejected; the builder catches it before POST.
节点格式为
{ id, type, name?, <body> }
,其中
type
是真实规范节点类型。主体字段可以扁平化(如下所示)或使用真实嵌套格式(
applicantLevel: { levelName }
)。
标准验证工作流(
kind: default|test
):
type
主体
applicantLevel
levelName
(字符串);可选
disableGoBack
exclusiveChoice
——(通过输出边缘分支)
actions
actions:
列表(如下所示)
manualReview
——
finalRejection
labels
或
buttonIds
操作工作流(仅
kind: actions
):每个
action*
类型对应标准类型的镜像——
actionApplicantLevel
、
actionExclusiveChoice
、
actionActions
、
actionManualReview
、
actionFinalRejection
的主体与不带
action
前缀的类型相同。额外一种类型:
actionApplicantTransition
(
levelName
——将验证转回
default
工作流的层级;该层级必须存在于已发布的
default
工作流
中,否则验证会返回
missingLevelsForTransition
错误)。
action*
类型仅在操作工作流中有效(该图在主验证后运行——通常用于打标签/添加备注/修改源密钥,或将申请人转回
default
工作流);标准类型仅在
default
/
test
中有效。混合使用会被拒绝;构建器会在POST前捕获该错误。

Actions block

操作块

Inside an
actions
or
actionActions
node:
yaml
- id: tag-and-note
  type: actions
  actions:
    - tag: ["High risk", "Manual review"]    # adds tags
    - note: "Flagged by workflow"            # adds applicant note
    - sourceKey: "manual-flow"               # overrides applicant source key
Each item must contain exactly one of:
tag
,
note
,
sourceKey
. Tag/note also take a richer object form for the niche cases:
yaml
    - tag:  { add: ["VIP"], remove: ["pending"], target: applicantAction }
    - note: { text: "Auto-note", target: applicantAction }
  • target
    (
    targetType
    ) is
    applicant
    by default.
    applicantAction
    targets the triggering action and is only valid in an action workflow (
    kind: actions
    ).
  • Not supported:
    kytCase
    (postponed — no public API for its
    blueprintId
    yet),
    riskScore
    ,
    recheck
    . (The old
    riskLevel
    action never existed — removed.)
在
actions
或
actionActions
节点内:
yaml
- id: tag-and-note
  type: actions
  actions:
    - tag: ["High risk", "Manual review"]    # 添加标签
    - note: "Flagged by workflow"            # 添加申请人备注
    - sourceKey: "manual-flow"               # 覆盖申请人源密钥
每个项必须包含以下之一:
tag
、
note
、
sourceKey
。标签/备注也支持更丰富的对象格式,用于特殊场景:
yaml
    - tag:  { add: ["VIP"], remove: ["pending"], target: applicantAction }
    - note: { text: "Auto-note", target: applicantAction }
  • target
    (
    targetType
    )默认值为
    applicant
    。
    applicantAction
    针对触发操作,且仅在操作工作流中有效(
    kind: actions
    )。
  • 不支持:
    kytCase
    (推迟——尚无公开API获取其
    blueprintId
    )、
    riskScore
    、
    recheck
    。(旧的
    riskLevel
    操作从未存在——已移除。)

Edge conditions (
condition:
)

边缘条件(
condition:
)

An edge may carry a
condition:
— the real Sumsub condition AST, written directly (there is no expression mini-language):
yaml
condition:
  or:                                  # OR of AND-groups; branches evaluated in order, first match wins
    - and:
        - { op: eq, args: [ {exp: "applicant.country"}, {lit: "\"USA\""} ] }
        - { op: in, args: [ {exp: "poi.country"}, {exp: "clientLists.high_risk"} ] }
  • {exp: "<path>"}
    — an expression path, verbatim. Anything the engine accepts works, including bracket/quoted segments:
    applicant.metadata["balance"]
    ,
    clientLists["luhansk_people's_republic"]
    . It's the left side of a comparison, and also the right side for field-to-field and client-list checks (
    {exp: "clientLists.high_risk"}
    ).
  • {lit: "<json>"}
    — a literal, supplied already JSON-encoded as a string (the form the API stores):
    "\"USA\""
    ,
    "3"
    ,
    "true"
    ,
    "[\"UZB\", \"DEU\"]"
    . An un-encoded literal (
    {lit: "USA"}
    ) is rejected by the API with
    syntaxError
    , so the encoding is required.
  • op
    is one of the comparison operators (
    eq ne lt lte gt gte in notIn contains notContains containsAny containsAll startsWith endsWith match empty notEmpty eqIgnoreCase …
    ; full set in references/workflow-schema.md). The builder validates every
    op
    against its known set and rejects unknown or reserved ones (
    call
    ).
  • Negation is expressed with the
    not*
    operators (
    notIn
    ,
    notContains
    ,
    ne
    ,
    notEmpty
    , …) — do not set a
    negate
    flag; a top-level
    Condition.negate
    is rejected (UI-unsupported).
Client lists (
clientLists.<name>
) are tenant-defined named lists maintained in the Sumsub dashboard, used as an expression RHS with
in
/
notIn
/
containsAny
. This skill does not create them. An unknown list validates as an
invalidExpression
error notice (a missing-entity error, like
levelNotFound
) — the construct is valid, the list just isn't defined in that workspace.
The full
Condition
/
And
/
Criterion
AST, literal-encoding rules, and operator list live in references/workflow-schema.md; valid
exp
paths are in references/workflow-expressions.md.
边缘可携带
condition:
——直接使用真实Sumsub条件AST(无表达式迷你语言):
yaml
condition:
  or:                                  # AND组的OR;分支按顺序评估,第一个匹配项生效
    - and:
        - { op: eq, args: [ {exp: "applicant.country"}, {lit: "\"USA\""} ] }
        - { op: in, args: [ {exp: "poi.country"}, {exp: "clientLists.high_risk"} ] }
  • {exp: "<path>"}
    ——表达式路径,原样使用。引擎接受的任何内容都有效,包括括号/引号段:
    applicant.metadata["balance"]
    、
    clientLists["luhansk_people's_republic"]
    。它是比较的左侧,也可作为字段间比较和客户列表检查的右侧(
    {exp: "clientLists.high_risk"}
    )。
  • {lit: "<json>"}
    ——字面量,需已编码为JSON字符串(API存储的格式):
    "\"USA\""
    、
    "3"
    、
    "true"
    、
    "[\"UZB\", \"DEU\"]"
    。未编码的字面量(
    {lit: "USA"}
    )会被API以
    syntaxError
    拒绝,因此必须编码。
  • **
    op
    **是比较运算符之一(
    eq ne lt lte gt gte in notIn contains notContains containsAny containsAll startsWith endsWith match empty notEmpty eqIgnoreCase …
    ;完整列表见references/workflow-schema.md)。构建器会验证每个
    op
    是否属于已知集合,并拒绝未知或保留的运算符(
    call
    )。
  • 否定通过
    not*
    运算符表达(
    notIn
    、
    notContains
    、
    ne
    、
    notEmpty
    ……)——请勿设置
    negate
    标志;顶层
    Condition.negate
    会被拒绝(不支持UI)。
客户列表(
clientLists.<name>
)是租户定义的命名列表,在Sumsub控制面板中维护,与
in
/
notIn
/
containsAny
一起用作表达式右侧。本技能不创建客户列表。未知列表会被验证为
invalidExpression
错误通知(缺失实体错误,类似
levelNotFound
)——结构有效,但列表在该工作区中未定义。
完整的
Condition
/
And
/
Criterion
AST、字面量编码规则和运算符列表见references/workflow-schema.md;有效的
exp
路径见references/workflow-expressions.md。

Edge
on:
(review-decision routing)

边缘
on:
(审核决策路由)

on: approved
gates a transition on a level's review decision (
approved
/
rejected
/
resubmission
; pass an array for several) — a shorthand for the real
reviewDecisions:
field.
on:
/
condition:
are honored only on edges leaving an
exclusiveChoice
(or
actionExclusiveChoice
in an action workflow; the
reviewDecisions
match the upstream level's decision); a level's own out-edge is a single, unconditional transition. So to branch on a level's outcome you author the choice yourself: point the level at an
exclusiveChoice
with one plain edge, then put the
on:
/
condition:
branches on the choice's out-edges (evaluated in declared order, first match wins). The builder does not synthesize choices — it rejects
on:
/
condition:
on a level edge (and a level with >1 out-edge), with a message telling you to insert the choice.
on:
and
condition:
can coexist on a choice's out-edge.
Two primitives govern omitted decisions: (1) omitting
on:
makes an edge decision-agnostic — it doesn't check the decision; (2) a resubmitter stays on its level unless some branch on the immediate post-level choice names
resubmission
. Together, a no-
on:
edge in practice serves approved + rejected while resubmission retries by default (it's a deduction from these two, not a stored default set). Leave resubmission to the engine: don't name
resubmission
anywhere unless the user explicitly asks to cap or otherwise act on repeated attempts.
It is not part of a standard workflow — the engine's automatic retry is what almost everyone wants. Only if asked, see Iteration without cycles: the resubmission gate in references/workflow-patterns.md (and note: naming
resubmission
on a choice changes how its other edges behave — that section explains the interlock).
on: approved
根据层级的审核决策(
approved
/
rejected
/
resubmission
;可传入数组指定多个)管控过渡——是真实
reviewDecisions:
字段的简写。
on:
/
condition:
仅在离开
exclusiveChoice
的边缘上生效
(或操作工作流中的
actionExclusiveChoice
;
reviewDecisions
匹配上游层级的决策);层级自身的输出边缘是唯一、无条件的过渡。因此,要根据层级结果分支,需自行创建选择:将层级指向
exclusiveChoice
(使用一条普通边缘),然后在选择的输出边缘上设置
on:
/
condition:
分支(按声明顺序评估,第一个匹配项生效)。构建器不会自动生成选择——它会拒绝层级边缘上的
on:
/
condition:
(以及层级有多个输出边缘的情况),并提示插入选择。
on:
和
condition:
可共存于选择的输出边缘上。
两个基本规则适用于未指定的决策:(1) 省略
on:
会使边缘与决策无关——不检查决策;(2) 重新提交的申请人停留在原层级,除非紧邻层级后的选择的某个分支指定了
resubmission
。因此,无
on:
的边缘实际上会处理approved + rejected,而重新提交默认由引擎重试(这是从上述两条规则推导而来,而非存储的默认集合)。让引擎处理重新提交:除非用户明确要求限制或处理重复尝试,否则不要在任何地方指定
resubmission
。它不是标准工作流的一部分——引擎的自动重试几乎是所有人想要的行为。仅当用户要求时,请参阅references/workflow-patterns.md中的Iteration without cycles: the resubmission gate(注意:在选择上指定
resubmission
会改变其其他边缘的行为——该部分解释了连锁反应)。

Worked examples

示例

  • examples/country-routing.json
    — entry level → eligibility gate (region-reject vs IDV) → IDV → a second choice → manual-review on a rejected IDV. Two choices, because a level sits between them (the most common topology).
  • examples/action-workflow.json
    — small action-workflow that tags and notes after a level completes.
  • examples/aml-routing.json
    — client-list geo gate + post-AML routing on
    applicant.review.rejectLabels
    (PEP / SANCTIONS / ADVERSE_MEDIA) with
    containsAny
    and tag-driven fan-in (mirrors real-world AML topologies).
  • examples/country-routing.json
    ——入口层级→资格 gate(区域拒绝 vs IDV)→IDV→第二个选择→IDV拒绝后进入人工审核。两个选择,因为中间有一个层级(最常见的拓扑结构)。
  • examples/action-workflow.json
    ——小型操作工作流,在层级完成后打标签和添加备注。
  • examples/aml-routing.json
    ——客户列表地域 gate + 基于
    applicant.review.rejectLabels
    (PEP / SANCTIONS / ADVERSE_MEDIA)的AML后路由,使用
    containsAny
    和标签驱动的扇入(镜像真实世界AML拓扑结构)。

See also

另请参阅

  • references/workflow-schema.md — reference for every node type's body, the action model, the
    Condition
    AST + operators, the lifecycle, and the live-traffic guard.
  • references/workflow-expressions.md — the expression-path lookup dictionary.
  • references/workflow-notices.md — the validation-notice catalog: every
    notices[]
    key the
    validate
    /
    post
    endpoints return, with severity, params, and how to act (ground a reference vs. fix the spec). Look up any notice you get here.
  • references/workflow-patterns.md — composition patterns: how to decompose a KYC/KYB policy into the canonical pipeline spine and reusable primitives (strategy, not schema). Start here when the ask is "build me a workflow" rather than "fix this node".
  • references/workflow-schema.md——每个节点类型的主体、操作模型、
    Condition
    AST+运算符、生命周期和实时流量管控的参考文档。
  • references/workflow-expressions.md——表达式路径查找字典。
  • references/workflow-notices.md——验证通知目录:
    validate
    /
    post
    端点返回的所有
    notices[]
    键,包含严重性、参数和处理方法(关联引用 vs 修复规范)。遇到任何通知请在此查找。
  • references/workflow-patterns.md——组合模式:如何将KYC/KYB策略分解为标准流程框架和可复用原语(策略,而非规范)。当用户要求“帮我构建一个工作流”而非“修复这个节点”时,从此处开始。