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 | /forms | forms:write | name? |
POST | /forms/:formId/clone | forms:clone | disabledSections? |
POST | /forms/:formId/client-links | client-links:write | expiresInDays、defaultLanguage、hideBranding? |
GET | /forms | forms:read / forms:write | query: limit?, cursor? |
GET | /forms/:formId | forms:read / forms:write | — |
所有请求体与响应均为 JSON。forms:read scope 授予表单元数据的只读访问;forms:write 同时授予读写,因此只写入的密钥也免费获得读取权限。
1. 创建 API 密钥
打开 工作区设置 → API 密钥 并点击 创建 API 密钥。选择一个描述性的名称(建议每个集成使用一把密钥,例如 Production CRM 或 Staging 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-1 | Personal Information - Part 1 |
personal-info-page-2 | Personal Information - Part 2 |
visa-purpose-page | Purpose of Visa |
travel-companions-page | Travel Companions |
previous-us-travel-page | Previous U.S. Travel History |
address-and-phone-page | Address and Phone Details |
passport-page | Passport Information |
contact-info-page | Contact Information |
family-info-page | Family Information |
spouse-info-page | Spouse Information |
deceased-spouse-info-page | Deceased Spouse Information |
former-spouse-info-page | Former Spouse Information |
present-occupation-page | Current Occupation |
previous-occuptation-page | Previous Occupation |
additional-occuptation-page | Additional Occupation Details |
security-background-page-1 | Security Background - Part 1 |
security-background-page-2 | Security Background - Part 2 |
security-background-page-3 | Security Background - Part 3 |
security-background-page-4 | Security Background - Part 4 |
security-background-page-5 | Security Background - Part 5 |
student-visa-page-1 | Student Visa Details - Part 1 |
student-visa-page-2 | Student Visa Details - Part 2 |
temporary-visa-page | Temporary Visa Information |
crew-visa-page | Crew 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" }' 请求体
| 字段 | 必填 | 说明 |
|---|---|---|
expiresInDays | 是 | 1–365 的整数。无默认值——省略将返回 400 Bad Request。链接的 expiresAt 计算为 now + expiresInDays,底层令牌记录在该时刻自动删除。 |
defaultLanguage | 是 | defaultLanguage 的有效取值中的某个代码。决定返回的 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 决定。 |
expiresAt | ISO-8601 时间戳。届时对应的记录会被自动删除,因此过期后链接无法再次使用。 |
将 url 发送给你的客户。URL 中的令牌为单次用途,在 expiresAt 时过期——客户填写时无需额外认证。
撤销客户填写链接
没有用于撤销已签发客户链接的 v1 端点——请在控制台撤销(工作区 → 表单 → 共享 → 撤销链接)。撤销即时生效且不可撤回:链接的下一次请求返回 401。如怀疑泄露而无人能在 UI 上撤销,最安全的兜底方式是让链接自然过期(相应地限制 expiresInDays)。
defaultLanguage 的有效取值
defaultLanguage 控制收件人首次打开链接时填表界面的语言。使用下表中任意一个代码——其他取值将返回 400 Bad Request。短代码(en、cn、…)是 DS160.io 的内部 locale 标识符;BCP-47 列展示了我们在 <html lang>、hreflang 与 Intl.* API 中发出的形式。API 请求中请使用短代码;BCP-47 形式仅作参考。
| 代码 | 语言 | 母语名称 | BCP-47 |
|---|---|---|---|
en | English | English | en-US |
ru | Russian | Русский | ru-RU |
ro | Romanian | Română | ro-RO |
es | Spanish | Español | es-ES |
cn | Chinese | 中文 | zh-CN |
vi | Vietnamese | Tiếng Việt | vi-VN |
hi | Hindi | हिन्दी | hi-IN |
nl | Dutch | Nederlands | nl-NL |
把客户链接当作密码对待
url 是 bearer 凭据——任何持有者都可以在过期前填写表单,无第二因素。
- 通过可信渠道发送;不要发布到可能泄露的位置(公开聊天、被搜索引擎索引的页面、共享集合)。
- 在日志中遮盖
?token=。仅记录jticlaim 是安全的。 - 怀疑泄露时立即撤销——撤销即时生效且不可恢复。
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 字段。当没有更多页时,nextCursor 为 null。示例:
{
"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/forms 与 GET /workspaces/:workspaceId/forms/:formId)仅返回操作性元数据:id、name、status、workspaceId、userId、preferredConsulate、createdAt、archivedAt。它们不会暴露申请人的答案——通过 /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 /forms与POST /forms/:formId/clone不是幂等的,且每次调用都会消耗一个表单额度。网络超时后的简单重试会创建重复表单并消耗第二个额度。POST /forms/:formId/client-links同样不是幂等的,但不消耗额度——重试只会生成另一个具有自身jti与expiresAt的链接。- 所有
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 个月。
应规划的向后兼容性新增:
- 请求中会随时间出现新的可选字段。不发送它们的现有请求将继续工作。
- 响应中可能新增字段。将未知字段视为可忽略——出现未来字段时不要失败。
status、defaultLanguage与disabledSections将新增枚举值。请不要因未知值崩溃;记录日志并继续。
支持:
- 更高的速率限制、自定义域名、Webhook 早期访问与集成问题:请联系你的客户经理或 support@ds160.io。
- 针对 v1 API 的 bug 报告:同一地址。每个响应都带有
x-request-id头——请在你的报告中包含它,以便我们能在日志中精确定位该调用。