Skip to content

发起一次签署 ​

以“家长为孩子签署课程服务合同”为例:先确认人和家庭关系,再选文档、预览、发邮件,最后查询结果。所有请求都由你的后端发出,沿用快速开始中的 Base URL 和 Bearer API key。

下面是合成数据,不是真实客户记录,也不是生产响应截图。 ID 必须替换为目标组织查询得到的值;发送前确认收件邮箱是你控制的测试邮箱。示例模板文案仅说明接口结构,不能作为正式合同。

准备好哪些东西 ​

准备项怎么确认
组织GET /v1/me 返回预期的俱乐部
权限club.contacts.read、club.contacts.read_pii、club.compliance.read、club.compliance.write
家长、孩子及关系已在该俱乐部维护;孩子有准确生日,家长有可用邮箱
模板商户已配置课程、排期、价格及附加文档规则,并发布可用版本
签后文件(可选)只有需要在第 5 步核对签后文件时才要 club.compliance.read_files

本文不创建客户资料。如需补资料,使用商户后台,或新建一把带 club.contacts.write 的 key 并完成切换后调用人员接口——已有 key 的权限不可修改,见权限范围。字段分别由谁维护,见字段来源。

1. 找到家长和孩子 ​

先调用 GET /v1/club/contacts?search=Demo&page=1&pageSize=20。列表采用家庭视图,孩子不一定单独占一行;名称和联系方式也可能脱敏。不要直接取第一条结果当签署人。

选定候选家长后,调用 GET /v1/club/contacts/{id} 核对完整资料,再调用 GET /v1/club/contacts/{id}/households 查看家庭成员。例如,家长 3101 的家庭返回:

json
{
  "data": [
    {
      "id": "9900",
      "name": "Demo household",
      "members": [
        {
          "contactId": "3101",
          "relation": "primary_guardian",
          "firstName": "Alex",
          "lastName": "Demo",
          "birthday": "1985-05-06",
          "roles": ["PARENT"]
        },
        {
          "contactId": "3102",
          "relation": "dependent",
          "firstName": "Robin",
          "lastName": "Demo",
          "birthday": "2016-03-14",
          "roles": ["PLAYER"]
        }
      ]
    }
  ]
}

这里签字的人是家长 3101,合同涉及的参与者是孩子 3102。家庭记录用于核对,不是自动证明法律监护资格;有歧义时请商户确认,不要按同姓或邮箱推断关系。还应查询孩子详情,确认他属于当前俱乐部且资料完整。

如果搜索返回下面的结果,表示“本次查询未命中”,不是权限错误,也不能证明平台上不存在此人:

json
{
  "data": [],
  "meta": {
    "page": 1,
    "pageSize": 20,
    "total": 0
  }
}

先调整查询或请商户核对已有资料,不要因为空结果就自动重复建人。

2. 选合同及附加文档 ​

调用 GET /v1/club/compliance/document-templates?documentKind=service_contract&active=true。例如:

json
{
  "data": [
    {
      "id": "810",
      "templateKey": "demo-program-agreement",
      "title": "Demo program agreement",
      "documentKind": "service_contract",
      "ownerScope": "tenant_group",
      "groupId": "123",
      "active": true,
      "currentVersion": 3,
      "currentVersionId": "8103",
      "effectiveFrom": "2026-09-01T00:00:00.000Z",
      "aiSummary": null
    }
  ]
}

再读取 GET /v1/club/compliance/document-templates/{id},核对标题、字段定义及课程配置。需要版本历史时调用 GET /v1/club/compliance/document-templates/{id}/versions。

注意三个不同的值:模板 ID 是 810,版本号是 3,版本 ID 是 8103。发送时需要版本 ID,不是前两个值。如果 currentVersionId 为 null,先请商户发布版本。

对孩子调用 POST /v1/club/compliance/signing-requests/resolve-waivers,请求体如下。这里的 contactId 指参与者,不是之后收邮件的家长:

json
{
  "contactId": "3102",
  "pdsaTemplateId": "810"
}

成功时 HTTP 200,示例结果:

json
{
  "data": {
    "memberIsMinor": true,
    "dobMissing": false,
    "appendixMatch": "matched",
    "items": [
      {
        "waiverTemplateKey": "demo-risk-acknowledgment",
        "templateVersionId": "8201",
        "documentKind": "waiver",
        "title": "Demo risk acknowledgment",
        "applicability": "minorOnly",
        "reason": "Selected by the demo template rule",
        "defaultChecked": true
      }
    ]
  }
}

defaultChecked 是规则给出的默认选择,不是“法律义务已经满足”。由商户核对最终文档集合,并把确认后的版本放进下一步的 items。

如果返回 dobMissing: true、memberIsMinor: null,不能把孩子当成年人。先补生日再解析;年龄相关项未默认勾选,不等于不需要。appendixMatch: "ambiguous" 则需要人工确认运动项目对应的附加文档。

3. 预览,不发送 ​

调用 POST /v1/club/compliance/signing-requests/preview,不需要 Idempotency-Key:

json
{
  "contactId": "3101",
  "dependentContactId": "3102",
  "flowFlavor": "standard",
  "items": [
    {
      "documentKind": "service_contract",
      "templateVersionId": "8103",
      "required": true,
      "displayOrder": 0
    },
    {
      "documentKind": "waiver",
      "templateVersionId": "8201",
      "required": true,
      "displayOrder": 1
    }
  ]
}

contactId 现在指收邮件并签字的家长;dependentContactId 指孩子。普通代签合同也使用 flowFlavor: "standard",不要仅因为有孩子就切到 parental_consent,后者有独立的同意记录逻辑。

成功时 HTTP 200,返回每份文档一项:

json
{
  "data": [
    {
      "documentKind": "service_contract",
      "templateVersionId": "8103",
      "title": "Demo program agreement",
      "renderedMarkdown": "# Demo program agreement\n\nClub: Demo Club\nProgram: Demo program\nParticipant: [to be completed at signing]\nSigned at: [recorded at signing]\n"
    },
    {
      "documentKind": "waiver",
      "templateVersionId": "8201",
      "title": "Demo risk acknowledgment",
      "renderedMarkdown": "# Demo risk acknowledgment\n\n…\n"
    }
  ]
}

返回顺序对应请求里的 items,用 templateVersionId 对上是哪一份,不要按下标假定。

逐份检查 renderedMarkdown。检查俱乐部信息、课程、价格、顺序和必签项。预览不是最终签署文件:姓名等签署字段可能仍为 [to be completed at signing],时间及设备字段可能为 [recorded at signing]。创建时也可能补入电子签署同意项。

当前此预览接口走 Markdown 渲染;如果模板绑定的是 PDF/DOCX 等二进制母版,不要期待它在这里返回完整可视预览。请在商户签署流程核对文件,不要收到渲染错误后跳过核对继续发送。

4. 创建并发送邀请 ​

确认无误后,调用 POST /v1/club/compliance/signing-requests。请求头使用 Content-Type: application/json,并为本次发送设置唯一的 Idempotency-Key,例如 demo-signing-20260912-001。

json
{
  "contactId": "3101",
  "dependentContactId": "3102",
  "flowFlavor": "standard",
  "items": [
    {
      "documentKind": "service_contract",
      "templateVersionId": "8103",
      "required": true,
      "displayOrder": 0
    },
    {
      "documentKind": "waiver",
      "templateVersionId": "8201",
      "required": true,
      "displayOrder": 1
    }
  ],
  "ttlHours": 72
}

HTTP 201 示例:

json
{
  "data": {
    "signingRequestId": "9200",
    "status": "sent",
    "expiresAt": "2026-09-15T09:00:00.000Z",
    "consentBundled": false
  }
}

保存 signingRequestId,关联到你自己的业务记录。此例签署人已有有效电子签署同意,所以没有补项;若返回 consentBundled: true,表示本次补入或复用了当前同意项,最终项目列表以查询结果为准。

sent 表示请求已发起,不是邮箱送达回执。 SportsX 负责发送签署邮件;Partner API 不返回原始签署 token,不要用请求 ID 自行拼 deeplink。超时或 409 的处理见写请求与重试,不要换一个幂等键重复发信。

5. 查询签署结果 ​

调用 GET /v1/club/compliance/signing-requests/{id} 查询刚创建的请求 9200。例如,家长打开链接后,文档仍未签完:

json
{
  "data": {
    "id": "9200",
    "groupId": "123",
    "contactId": "3101",
    "status": "opened",
    "expiresAt": "2026-09-15T09:00:00.000Z",
    "sentAt": "2026-09-12T09:00:00.000Z",
    "firstOpenedAt": "2026-09-12T09:10:00.000Z",
    "completedAt": null,
    "declinedAt": null,
    "cancelledAt": null,
    "flowFlavor": "standard",
    "dependentContactId": "3102",
    "signingTokenVersion": "1",
    "createdByContactId": null,
    "createdAt": "2026-09-12T09:00:00.000Z",
    "updatedAt": "2026-09-12T09:10:00.000Z",
    "items": [
      {
        "id": "9300",
        "documentKind": "service_contract",
        "templateVersionId": "8103",
        "required": true,
        "displayOrder": 0,
        "itemStatus": "pending",
        "confirmedAt": null,
        "signedAt": null
      },
      {
        "id": "9301",
        "documentKind": "waiver",
        "templateVersionId": "8201",
        "required": true,
        "displayOrder": 1,
        "itemStatus": "pending",
        "confirmedAt": null,
        "signedAt": null
      }
    ],
    "signedDocuments": []
  }
}

opened 只表示打开过,不能向你的业务系统写“已签署”。此时 completedAt 为 null,文档项目尚未签署,signedDocuments 为空。

状态接入方怎么处理
draft、sent、opened、in_progress尚未完成;低频、有次数上限地查询,遵守限流响应
partially_completed不要标记全部完成;检查逐项结果,交给商户核对
completed核对完成时间及逐项证据,再同步业务完成状态
declined、cancelled、expired保留当前结果,由商户决定后续动作,不自动重新发起

需要追踪查看、签署等过程时,调用 GET /v1/club/compliance/signing-requests/{id}/events。

取签后文件需要另外授予 club.compliance.read_files(已有 key 改不了权限,须新建并切换)。completed 后重新查询请求详情,signedDocuments 里每项的 finalFileAssetId 就是签署完成的文件;结业证明另有 completionCertificateFileAssetId,可能为 null。拿这个 ID 调:

json
{
  "data": {
    "url": "https://files.example.com/demo-signed.pdf?sig=…",
    "expiresAt": "2026-09-12T10:00:00.000Z"
  }
}

url 是限时地址,过期就失效,不要存进你的业务库当永久链接,需要时重新换一次。每次换取都会计入审计。

验收时至少完成一次“受控邮箱收信 → 家长打开 → 逐项签署 → 查询完成状态 → 核对签后文件”。API 返回正确,不代表字段、邮件、文件及最终证据都已经验收通过。