本页面译自英文。英文版本为权威版本——如发现任何不一致之处,请以英文版为准。 EN

API 密钥与程序化访问

通过限定于工作区的 API 密钥,从你自己的后端创建表单、克隆表单并生成客户填写链接。

如果你已经运行 CRM、案件管理系统或内部门户,无需登录控制台逐个创建表单。API 密钥让你的后端直接调用 DS160.io v1 API——按你的节奏创建表单、克隆表单并生成客户填写链接。

API 密钥仅在 Business 工作区中可用。每把密钥都绑定到单个工作区,仅能在该工作区内执行操作。

API 基础 URL

所有 v1 端点都以以下 URL 为根:

https://ds160.io/api/v1

本文档可能托管在你机构的白标域名下,但 API 调用始终发往 ds160.io。本页所有代码示例都使用完整 URL,便于你复制粘贴而无需改写。

端点参考

/v1 是稳定接口——路径与字段名不会在没有 /v2 主版本的情况下变更(参见版本与支持)。下表中的路径相对于工作区前缀 /workspaces/:workspaceId/ 显示;完整的请求和响应结构在各端点的章节中说明。

方法路径Scope请求体
POST/formsforms:writename?
POST/forms/:formId/cloneforms:clonedisabledSections?
POST/forms/:formId/client-linksclient-links:writeexpiresInDaysdefaultLanguagehideBranding?
GET/formsforms:read / forms:writequery: limit?, cursor?
GET/forms/:formIdforms:read / forms:write

所有请求体与响应均为 JSON。forms:read scope 授予表单元数据的只读访问;forms:write 同时授予读写,因此只写入的密钥也免费获得读取权限。

1. 创建 API 密钥

打开 工作区设置 → API 密钥 并点击 创建 API 密钥。选择一个描述性的名称(建议每个集成使用一把密钥,例如 Production CRMStaging webhook handler),然后选择该集成所需的 scope:

  • forms:read — 仅读取表单元数据(列出 / 获取)
  • forms:write — 创建表单;同时授予读取
  • forms:clone — 复制已有表单
  • client-links:write — 生成客户填写链接

只有工作区 所有者管理员 可以创建密钥。

点击 创建密钥 后,平台会仅一次显示完整的密钥。请立即将其复制到你的密钥管理工具——窗口关闭后,仅会显示最后四位字符。如果丢失密钥,请撤销并重新生成。

2. 鉴权

所有 v1 端点都需要 Authorization: Bearer <secret> 头。

export DS160_KEY="...the secret you just copied..."
export DS160_WORKSPACE="your-workspace-id"

curl https://ds160.io/api/v1/workspaces/$DS160_WORKSPACE/forms \
  -H "Authorization: Bearer $DS160_KEY"

工作区 ID 出现在控制台每个页面的 URL 中 (/workspaces/<workspaceId>/...)。密钥的工作区在服务器端绑定——用错误的密钥调用其他工作区的端点会返回 403 Forbidden

提示: 本页所有片段的语言标签是同步的。选择一次语言,整页都会跟随。

3. 创建表单

curl -X POST https://ds160.io/api/v1/workspaces/$DS160_WORKSPACE/forms \
  -H "Authorization: Bearer $DS160_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Smith / B1 — 2026-05" }'
# → { "formId": "65f2…" }

每次调用都会消耗一个表单额度(与从控制台创建表单相同)。如果工作区额度已用尽,调用会返回 402 Payment Required——请先充值再重试。

可选字段 name 用于为表单设置易读的名称(最多 200 个字符)。省略该字段将自动生成名称——无论如何,您之后都可以在控制台中重命名该表单。

4. 克隆已有表单

如果你有一个经常复用的模板表单(例如同一雇主的 J-1 项目),克隆它而不是从头开始。可选地传递 disabledSections 以从复制内容中省略特定页面——所列项在新表单中会被替换为空字段,由客户重新填写:

curl -X POST https://ds160.io/api/v1/workspaces/$DS160_WORKSPACE/forms/$SOURCE_FORM_ID/clone \
  -H "Authorization: Bearer $DS160_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "disabledSections": ["spouse-info-page", "security-background-page-5"] }'
# → { "formId": "65f3…" }

克隆同样消耗一个表单额度,与新建表单一致。

disabledSections 的有效取值

传入空数组(或省略该字段)以复制所有页面。否则使用下表中任意标识符的组合——其他取值将返回 400 Bad Request

标识符页面
personal-info-page-1Personal Information - Part 1
personal-info-page-2Personal Information - Part 2
visa-purpose-pagePurpose of Visa
travel-companions-pageTravel Companions
previous-us-travel-pagePrevious U.S. Travel History
address-and-phone-pageAddress and Phone Details
passport-pagePassport Information
contact-info-pageContact Information
family-info-pageFamily Information
spouse-info-pageSpouse Information
deceased-spouse-info-pageDeceased Spouse Information
former-spouse-info-pageFormer Spouse Information
present-occupation-pageCurrent Occupation
previous-occuptation-pagePrevious Occupation
additional-occuptation-pageAdditional Occupation Details
security-background-page-1Security Background - Part 1
security-background-page-2Security Background - Part 2
security-background-page-3Security Background - Part 3
security-background-page-4Security Background - Part 4
security-background-page-5Security Background - Part 5
student-visa-page-1Student Visa Details - Part 1
student-visa-page-2Student Visa Details - Part 2
temporary-visa-pageTemporary Visa Information
crew-visa-pageCrew Visa Information

仅与表单签证类别相关的章节会实际出现;列出源表单中不存在的页面没有任何影响。

5. 生成客户填写链接

表单存在后,即可生成带令牌的 URL,供客户用于填写:

curl -X POST https://ds160.io/api/v1/workspaces/$DS160_WORKSPACE/forms/$FORM_ID/client-links \
  -H "Authorization: Bearer $DS160_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "expiresInDays": 7, "defaultLanguage": "en" }'

请求体

字段必填说明
expiresInDays1–365 的整数。无默认值——省略将返回 400 Bad Request。链接的 expiresAt 计算为 now + expiresInDays,底层令牌记录在该时刻自动删除。
defaultLanguagedefaultLanguage 的有效取值中的某个代码。决定返回的 url 的语言前缀,以及表单初始打开时的语言。
hideBranding布尔值。为 true 时表单无品牌显示——本链接将隐藏机构的 whitelabel 徽标与主题。默认为 false(若已配置,将显示工作区的 whitelabel 品牌)。

示例响应(200 OK

{
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI2NmMxYmQ0ZjZmNGFkNTQwMzMxNDhmYmEiLCJmb3JtSWQiOiI2NWYyYTkxMTNkZjAxYzAwMTI3YjY4MmEiLCJ3b3Jrc3BhY2VJZCI6IjY1ZjJhOTAwM2RmMDFjMDAxMjdiNjgwYSIsImV4cCI6MTc0ODA0ODI0NywiaWF0IjoxNzQ3NDQzNDQ3LCJkZWZhdWx0TGFuZ3VhZ2UiOiJlbiIsImhpZGVCcmFuZGluZyI6ZmFsc2V9.SiGNATuRe",
    "url": "https://intake.your-agency.com/client-intake/65f2a9113df01c00127b682a?token=eyJhbGciOi…",
    "expiresAt": "2026-05-24T01:50:46.000Z"
}
字段说明
token同样嵌入在 url 中的 JWT。通常你无需直接使用——客户打开 url,服务器验证嵌入的令牌。令牌的 jti claim 是该链接的唯一 ID;如果以后可能需要撤销特定链接,请在你的系统中持久化它(参见撤销客户填写链接)。
url可分享的链接。如果工作区的自定义域名已验证并配置 SSL,则使用自定义域名;否则回退到 ds160.io。路径的 locale 前缀 (/es, /cn, …) 根据 defaultLanguage 决定。
expiresAtISO-8601 时间戳。届时对应的记录会被自动删除,因此过期后链接无法再次使用。

url 发送给你的客户。URL 中的令牌为单次用途,在 expiresAt 时过期——客户填写时无需额外认证。

撤销客户填写链接

没有用于撤销已签发客户链接的 v1 端点——请在控制台撤销(工作区 → 表单 → 共享 → 撤销链接)。撤销即时生效且不可撤回:链接的下一次请求返回 401。如怀疑泄露而无人能在 UI 上撤销,最安全的兜底方式是让链接自然过期(相应地限制 expiresInDays)。

defaultLanguage 的有效取值

defaultLanguage 控制收件人首次打开链接时填表界面的语言。使用下表中任意一个代码——其他取值将返回 400 Bad Request。短代码(encn、…)是 DS160.io 的内部 locale 标识符;BCP-47 列展示了我们在 <html lang>hreflangIntl.* API 中发出的形式。API 请求中请使用短代码;BCP-47 形式仅作参考。

代码语言母语名称BCP-47
enEnglishEnglishen-US
ruRussianРусскийru-RU
roRomanianRomânăro-RO
esSpanishEspañoles-ES
cnChinese中文zh-CN
viVietnameseTiếng Việtvi-VN
hiHindiहिन्दीhi-IN
nlDutchNederlandsnl-NL

把客户链接当作密码对待

url 是 bearer 凭据——任何持有者都可以在过期前填写表单,无第二因素。

  • 通过可信渠道发送;不要发布到可能泄露的位置(公开聊天、被搜索引擎索引的页面、共享集合)。
  • 在日志中遮盖 ?token=。仅记录 jti claim 是安全的。
  • 怀疑泄露时立即撤销——撤销即时生效且不可恢复。
  • expiresInDays 是一种权衡:越短 = 泄露窗口越小,越长 = 重新签发的摩擦越少。

6. 列出或获取表单

# All forms in the workspace
curl https://ds160.io/api/v1/workspaces/$DS160_WORKSPACE/forms \
  -H "Authorization: Bearer $DS160_KEY"

# A single form by id
curl https://ds160.io/api/v1/workspaces/$DS160_WORKSPACE/forms/$FORM_ID \
  -H "Authorization: Bearer $DS160_KEY"

这些读取端点接受 forms:read scope(只读)或 forms:write(同时授予读取)。

分页

GET /v1/workspaces/:workspaceId/forms 支持分页。通过 ?limit=?cursor= 查询参数遍历结果:

查询参数含义
limit本页返回的最大表单数量。默认 50,硬上限 200
cursor上一页 nextCursor 中的表单 id。首次请求时省略,从最新的表单开始。

每个响应都包含 nextCursor 字段。当没有更多页时,nextCursornull。示例:

{
    "forms": [
        { "id": "65f2a911…", "name": "Smith / B1", "status": "in_progress", "workspaceId": "65f2a900…", "userId": "65f2a8f0…", "preferredConsulate": null, "createdAt": "2026-05-14T09:12:33.000Z", "archivedAt": null },
        { "id": "65f2a8a3…", "name": "Garcia / F1", "status": "completed", "workspaceId": "65f2a900…", "userId": "65f2a8f0…", "preferredConsulate": "MAD", "createdAt": "2026-05-13T18:01:09.000Z", "archivedAt": null }
    ],
    "nextCursor": "65f2a8a3…"
}

继续翻页时在下次请求中传入 ?cursor=65f2a8a3…&limit=50。表单按 id 从新到旧排序,因此即使两次调用之间创建了新表单,游标也会固定你的读取位置——你不会看到重复项,也不会错过在你开始分页时已存在的内容。

表单 status 取值

每个表单元数据响应中的 status 字段是以下之一:

取值何时适用
not_started表单已创建(通过 API 或控制台),但尚未回答任何字段。
in_progress至少已回答一个字段。大多数表单的生命周期主要处于该状态。
completed申请人已完成并提交填写;机构现在可以下载/提交 DS-160 PDF。
archived表单已从控制台归档。除非显式查询已归档项,默认列表结果会将其排除。

线路格式使用下划线(in_progress,而非 in-progress)。按上表中的字符串直接比较始终有效。

通过 API 访问 PII

v1 表单元数据端点(GET /workspaces/:workspaceId/formsGET /workspaces/:workspaceId/forms/:formId)仅返回操作性元数据idnamestatusworkspaceIduserIdpreferredConsulatecreatedAtarchivedAt。它们不会暴露申请人的答案——通过 /v1 无法访问姓名、出生日期、护照号、旅行历史或任何其他 DS-160 字段。

完成的 DS-160 PDF 与完整字段数据仅可通过控制台访问,归属于带审计日志的申请人所有者。如果你的集成需要读取申请人输入的数据,请通过你自己的客户填写流程(你的 CRM 先收集数据,再推送至 DS160.io)——切勿假设 API 会将其回传。

速率限制

每把密钥的限制是 每分钟 60 次请求。每个响应都会包含:

头部含义
X-RateLimit-Limit上限(60)。
X-RateLimit-Remaining当前窗口内剩余请求数。
X-RateLimit-Reset窗口重置时的 Unix 时间戳。

超过限制时,API 返回 429 Too Many Requests 并附带 Retry-After 头。如果你的集成确有提高上限的需求,请联系支持——我们可以为特定密钥提升上限。

幂等性与重试

v1 API 支持 Idempotency-Key 头。具体来说:

  • POST /formsPOST /forms/:formId/clone 不是幂等的,且每次调用都会消耗一个表单额度。网络超时后的简单重试会创建重复表单并消耗第二个额度。
  • POST /forms/:formId/client-links 同样不是幂等的,但不消耗额度——重试只会生成另一个具有自身 jtiexpiresAt 的链接。
  • 所有 GET 端点都可放心重试。

create/clone 的推荐重试模式: 如果 POST /forms(或 /clone)超时或返回 5xx,请勿盲目重试。改为调用 GET /workspaces/:workspaceId/forms(表单按新到旧排序),检查最近几秒内是否创建了你提供的 name 对应的表单。若是,将原调用视为成功并跳过重试;若否,原写入确未生效,重试是安全的。

我们可能在未来某个小版本中加入 Idempotency-Key 支持;集成应做好规划,但目前不要依赖它。

撤销密钥

如果密钥泄露、退役或仅是闲置,请通过 工作区设置 → API 密钥 → 撤销 撤销。撤销立即生效——下一次使用该密钥的请求将返回 401 Unauthorized。已撤销的密钥仍保留在列表中以供审计,但无法重新启用;如需继续运行集成,请重新生成一把。

最佳实践

  • 每个集成使用一把密钥。 系统下线时更易于精准撤销,控制台中的 Last used 列可显示哪些集成仍在使用。
  • 最小 scope。 仅生成客户链接的密钥不应拥有 forms:clone。更小的 scope 意味着泄露时的爆炸半径更小。
  • 将密钥存放在密钥库中。 永远不要提交到版本控制或嵌入前端 bundle——每一个浏览器标签都会曝光它们。
  • 定期轮换。 生成新密钥,切换集成,再撤销旧密钥。控制台显示密钥最近使用时间,便于在撤销前确认切换完成。

错误

所有非 2xx 响应都共享同一 JSON 结构:

{ "error": "可读的错误消息" }

error 是字符串。对于由 schema 验证引发的 400 Bad Request,它是描述每条违规约束的序列化 JSON 数组;对于其他情形,它是可直接展示或记录的简短消息。

状态何时发生示例响应体
400请求体或路径参数未通过验证(必填字段缺失、值越界、未知枚举值等)。{ "error": "[{\"keyword\":\"required\",\"params\":{\"missingProperty\":\"expiresInDays\"}}]" }
401缺少 Authorization 头、Bearer 令牌格式错误、密钥不匹配,或密钥已撤销。{ "error": "Missing API key" }  ·  { "error": "Invalid API key" }  ·  { "error": "API key revoked" }
402工作区表单额度耗尽。仅 create/clone。请充值后重试。{ "error": "Workspace has no remaining credits" }
403密钥缺少所需 scope,或路径中的 :workspaceId 与密钥绑定的工作区不匹配。{ "error": "Missing required scope: forms:write" }  ·  { "error": "API key does not match workspace" }
404未找到表单,或表单存在但隶属其他工作区。(作为 404 而非 403 返回,以避免跨工作区泄露存在性。){ "error": "Form not found" }
429单密钥速率限制超限(默认 60 次/分钟)。等待 Retry-After(秒)后再重试。{ "error": "Rate limit exceeded" }
5xx临时服务器错误。GET 可放心重试;写入端点请先参考幂等性与重试再重试。

端到端示例

完整的填写流程——创建表单、签发客户链接、发送给客户、轮询完成状态——以 curl 演示。实际集成会使用各自的 HTTP 客户端;任何语言下的形态都相同。

# 0. 凭据(一次设置)
export DS160_KEY="..."
export DS160_WORKSPACE="65f2a900..."

# 1. 创建表单。从响应中捕获 formId。
FORM_ID=$(curl -s -X POST \
  https://ds160.io/api/v1/workspaces/$DS160_WORKSPACE/forms \
  -H "Authorization: Bearer $DS160_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Smith / B1 — 2026-05" }' \
  | jq -r .formId)

# 2. 签发一个 14 天有效的客户填写链接。
LINK_JSON=$(curl -s -X POST \
  https://ds160.io/api/v1/workspaces/$DS160_WORKSPACE/forms/$FORM_ID/client-links \
  -H "Authorization: Bearer $DS160_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "expiresInDays": 14, "defaultLanguage": "en" }')

CLIENT_URL=$(echo "$LINK_JSON" | jq -r .url)

# 3. 通过你常用的渠道(邮件、安全门户等)将 $CLIENT_URL
#    发送给申请人。申请人填写表单。

# 4. 轮询完成状态。状态转换:
#    not_started → in_progress → completed
curl -s https://ds160.io/api/v1/workspaces/$DS160_WORKSPACE/forms/$FORM_ID \
  -H "Authorization: Bearer $DS160_KEY" \
  | jq .status
# → 当申请人完成后变为 "completed"

# 5. 从控制台下载 DS-160 PDF
#    (目前没有 v1 端点)。

关于此流程的一些说明:

  • 轮询频率:每 5–15 分钟足够。在 60 次/分钟的上限下,单把密钥可以轻松轮询数千个进行中的表单。
  • 暂未提供 Webhook。如果你需要完成时的推送通知,请关注本页或联系支持——这在路线图中。
  • 第 5 步(下载完成的 PDF)目前需要控制台会话。v1 API 有意不暴露申请人答案——参见通过 API 访问 PII

版本与支持

稳定性。 /v1 是稳定的 API 表面。我们会在 /v1 中添加新的可选字段、新端点与新枚举值,而不增加主版本号;但我们不会重命名、删除或更改任何现有字段的类型。如果确需破坏性变更,将以 /v2 发布,且 /v1 至少与之共存 12 个月。

应规划的向后兼容性新增:

  • 请求中会随时间出现新的可选字段。不发送它们的现有请求将继续工作。
  • 响应中可能新增字段。将未知字段视为可忽略——出现未来字段时不要失败。
  • statusdefaultLanguagedisabledSections 将新增枚举值。请不要因未知值崩溃;记录日志并继续。

支持:

  • 更高的速率限制、自定义域名、Webhook 早期访问与集成问题:请联系你的客户经理或 support@ds160.io
  • 针对 v1 API 的 bug 报告:同一地址。每个响应都带有 x-request-id 头——请在你的报告中包含它,以便我们能在日志中精确定位该调用。