快速入门
Connectelly External API 采用两步认证。先用 API 密钥换取短期有效的 access token,再使用该令牌调用 API。
- 申请成为合作伙伴并获取 API 密钥 — 签约完成后由主管理员发放密钥。
- 获取 access token —
POST /api/external/v1/token - 使用令牌调用 —
Authorization: Bearer <access_token>
# 1) 获取令牌 (API 密钥仅在此处使用)
curl -X POST https://connectelly.com/api/external/v1/token \
-H "X-Connectelly-Api-Key: ck_live_..."
# → {"access_token":"eyJ...","token_type":"Bearer","expires_in":1800,
# "event_id":"af2f95b4..."} ← eventId 从这里获取
# 2) 使用获取的令牌发送联系人
curl -X POST https://connectelly.com/api/external/v1/events/<eventId>/contacts \
-H "Authorization: Bearer eyJ..." \
-H "Content-Type: application/json" \
-d '{"external_id":"REG-001","name":"John Smith","email":"john@example.com"}'
返回 200 即表示保存成功。应用用户扫描 REG-001 的二维码时,将自动采集线索。
身份验证
两步认证
长期有效的 API 密钥仅用于获取令牌,实际的 API 调用使用短期有效的 access token。日常流量中不携带密钥,因此不会通过合作伙伴的服务器日志或代理泄露。
API 密钥
- 格式:
ck_live_<32 url-safe base64> - 仅在
POST /api/external/v1/token中以X-Connectelly-Api-Key请求头发送 - 按活动发放,不能用于其他活动 (404 event_not_found)
- 仅限服务端使用。切勿写入客户端代码或提交到公开仓库
Access Token
- 有效期 30 分钟(1800 秒)。过期后请重新获取。
- 所有 API 调用都需携带
Authorization: Bearer <access_token>。 - 密钥被吊销后,已发放的令牌也会立即失效。
密钥泄露时
请立即通知管理员吊销密钥。吊销后,该密钥及其发放的所有令牌都会立即被拦截。重新发放新密钥后,旧密钥会在 24 小时宽限期后失效,因此可以无中断地完成替换。
IP 限制 (推荐)
登记允许的 IP/CIDR 后,只有这些地址才能获取令牌。即使密钥泄露,也无法在其他位置使用。请联系管理员进行设置。
令牌 API
用 API 密钥换取短期有效的 access token。只有这个端点接受请求头中的 API 密钥,其余所有 API 都使用在此获取的令牌调用。
请求
POST /api/external/v1/token
| 请求头 | 必填 | 说明 |
|---|---|---|
X-Connectelly-Api-Key | 是 | 已发放的 API 密钥 (ck_live_...) |
无需请求 body。
curl -X POST https://connectelly.com/api/external/v1/token \
-H "X-Connectelly-Api-Key: ck_live_..."
响应 200
{
"access_token": "eyJhbGciOiJIUzM4NCJ9...",
"token_type": "Bearer",
"expires_in": 1800,
"expires_at": "2026-09-07T05:18:35Z",
"event_id": "af2f95b402794c8f9289360fc8765b8e"
}
| 字段 | 类型 | 说明 |
|---|---|---|
| access_token | string | 用于之后所有请求的 Authorization 请求头 |
| token_type | string | 固定为 Bearer |
| expires_in | integer | 剩余有效时间(秒)。默认 1800 = 30 分钟 |
| expires_at | string | 过期时间 (ISO-8601 UTC) |
| event_id | string | 该密钥绑定的活动 ID。直接用于之后所有端点路径中的 {eventId} |
eventId 从哪里获取?
API 密钥绑定单个活动发放,因此无需单独管理 eventId。可以通过以下两种方式确认:
- 令牌响应中的
event_id— 直接从上面的响应中获取。建议使用此值,而不要硬编码。 - 发放密钥时告知 — 负责人交付 API 密钥时会一并告知。
传入其他活动的 eventId 会返回 404 event_not_found。
错误
| HTTP | code | 原因 |
|---|---|---|
| 401 | auth_missing | 缺少请求头 |
| 401 | auth_invalid | 无法识别密钥 |
| 401 | key_expired | 密钥已过期 (或轮换宽限期已结束) |
| 401 | key_revoked | 密钥已被吊销 |
| 403 | ip_not_allowed | IP 不在允许列表中 |
运维建议
- 不要每次请求都获取令牌,请缓存令牌。建议在过期前 1~2 分钟刷新。
- 收到 401
token_invalid时,重新获取令牌并仅重试 1 次。 - 令牌只保存在服务器内存中,不要写入日志或 URL。
创建 / 更新联系人
创建或更新 1 条联系人。如果 external_id 已存在,则覆盖(UPSERT)。在登记时调用,应用扫描二维码时即可自动采集线索。
POST /api/external/v1/events/{eventId}/contacts
请求头
| 请求头 | 必填 | 说明 |
|---|---|---|
Authorization | 是 | Bearer <access_token> |
Content-Type | 是 | application/json |
Idempotency-Key | 否 | 使用相同的键重试时不会重新处理,而是返回第一次的响应。参见「幂等性」 |
请求 body
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| external_id | string | 是 | 登记系统中的唯一 ID,最多 100 个字符。必须与二维码的值一致 |
| name | string | 否 | 姓名,最多 200 个字符 |
| string | 否 | 邮箱。保存时使用 AES-256-GCM 加密 | |
| phone | string | 否 | 电话号码。保存时加密 |
| company | string | 否 | 公司名称,最多 200 个字符 |
| position | string | 否 | 职位,最多 100 个字符 |
| extra | object | 否 | 自由格式 JSON,用于合作伙伴自定义字段 |
curl -X POST https://connectelly.com/api/external/v1/events/<eventId>/contacts \
-H "Authorization: Bearer eyJ..." \
-H "Content-Type: application/json" \
-d '{
"external_id": "REG-001",
"name": "John Smith",
"email": "john@example.com",
"phone": "010-1234-5678",
"company": "Acme Inc.",
"position": "CEO",
"extra": { "booth": "A-12" }
}'
响应 200
原样返回刚刚发送的值(用于确认写入)。字段格式与查询联系人相同,但此响应中的 qr_image_url 始终为 null — 请通过查询联系人或联系人列表获取二维码图片 URL。
{
"id": "con_af2f95b4_REG-001",
"object": "external_contact",
"event_id": "af2f95b402794c8f9289360fc8765b8e",
"external_id": "REG-001",
"name": "John Smith",
"email": "john@example.com",
"phone": "010-1234-5678",
"company": "Acme Inc.",
"position": "CEO",
"extra": {"booth": "A-12"},
"created_at": "2026-09-07T04:50:38Z",
"updated_at": "2026-09-07T04:50:38Z",
"qr_image_url": null
}
错误
| HTTP | code | 原因 |
|---|---|---|
| 400 | validation_failed | 缺少 external_id 或字段长度超限 |
| 400 | invalid_json | 请求体不是有效的 JSON |
| 401 | token_missing / token_invalid | 缺少令牌 / 令牌伪造或过期 |
| 404 | event_not_found | 活动不存在或超出密钥范围 |
| 409 | idempotency_conflict | 相同的 Idempotency-Key 用于不同的请求 |
| 409 | idempotency_in_progress | 使用该 Idempotency-Key 的第一次请求仍在处理中 |
| 429 | rate_limited | 超出速率限制 |
批量导入
一次最多 UPSERT 500 条。适用于初始数据导入或夜间同步。
POST /api/external/v1/events/{eventId}/contacts/batch
请求 body
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| contacts | array | 是 | 联系人数组,1~500 条。每一项的格式与单条创建相同 |
curl -X POST https://connectelly.com/api/external/v1/events/<eventId>/contacts/batch \
-H "Authorization: Bearer eyJ..." \
-H "Content-Type: application/json" \
-d '{
"contacts": [
{ "external_id": "REG-001", "name": "John Smith", "email": "john@example.com" },
{ "name": "Jane Doe", "email": "jane@example.com" }
]
}'
响应 200
示例中的第二项缺少 external_id,因此只有该项失败,其余项正常保存。
{
"total": 2,
"created": 1,
"updated": 0,
"failed": 1,
"errors": [
{
"external_id": null,
"code": "validation_failed",
"message": "external_id is required.",
"param": "external_id"
}
]
}
| 字段 | 类型 | 说明 |
|---|---|---|
| total | integer | 请求中的总条数 |
| created | integer | 新建条数 |
| updated | integer | 覆盖已有数据的条数 |
| failed | integer | 未保存的条数 |
| errors | array | 仅包含失败项 (external_id、code、message、param)。全部成功时为空数组 |
注意
- 数组为空或超过 500 条时,整个请求返回 400
validation_failed。 - 允许部分成功。即使部分项失败,正确的项也会被保存。只需修正
errors中的项并重新发送。由于是 UPSERT,不会产生重复。 - 大批量导入时,请参考「速率限制」控制调用间隔,以避免 429。
- 支持
Idempotency-Key请求头 — 超时后使用相同的键重试,请求只会被处理一次。参见「幂等性」。
联系人列表
一次性返回该活动下保存的全部联系人。可用于核对已发送的数据,或批量获取用于打印胸卡的二维码图片。
GET /api/external/v1/events/{eventId}/contacts
curl https://connectelly.com/api/external/v1/events/<eventId>/contacts \
-H "Authorization: Bearer eyJ..."
响应 200
没有查询参数,也不分页。contacts 中的每一项与查询联系人响应的字段相同,姓名、邮箱、电话号码也以相同方式脱敏。
{
"total": 2,
"returned": 2,
"truncated": false,
"contacts": [
{
"id": "con_af2f95b4_REG-002",
"object": "external_contact",
"event_id": "af2f95b402794c8f9289360fc8765b8e",
"external_id": "REG-002",
"name": "J******e",
"email": "ja**@example.com",
"phone": null,
"company": null,
"position": null,
"extra": null,
"created_at": "2026-09-07T05:02:11Z",
"updated_at": "2026-09-07T05:02:11Z",
"qr_image_url": "https://cdn.connectelly.com/qr/af2f95b402794c8f9289360fc8765b8e/REG-002.png"
},
{
"id": "con_af2f95b4_REG-001",
"object": "external_contact",
"event_id": "af2f95b402794c8f9289360fc8765b8e",
"external_id": "REG-001",
"name": "J********h",
"email": "jo**@example.com",
"phone": "010-****-5678",
"company": "Acme Inc.",
"position": "CEO",
"extra": {"booth": "A-12"},
"created_at": "2026-09-07T04:50:38Z",
"updated_at": "2026-09-07T04:50:38Z",
"qr_image_url": "https://cdn.connectelly.com/qr/af2f95b402794c8f9289360fc8765b8e/REG-001.png"
}
]
}
| 字段 | 类型 | 说明 |
|---|---|---|
| total | integer | 该活动下保存的联系人总数 |
| returned | integer | 本次响应中包含的联系人数(最多 10,000) |
| truncated | boolean | 为 true 时表示达到 10,000 条上限,仅返回了最新的 10,000 条。遇到此情况请联系我们 |
| contacts | array | 联系人列表,按登记时间从新到旧排列 |
- 按首次登记时间(
created_at)从新到旧排序。更新(UPSERT)不会改变顺序。 - 列表查询不会生成新的二维码图片。
qr_image_url是登记时图片的保存地址,若登记时生成失败,该链接可能无法打开。用查询联系人 API 查询一次该联系人即可生成图片。
错误
| HTTP | code | 原因 |
|---|---|---|
| 404 | event_not_found | 活动不存在或超出密钥范围 |
查询联系人
查询 1 条已登记的联系人。
GET /api/external/v1/events/{eventId}/contacts/{externalId}
响应 200 — 个人信息已脱敏
查询响应中的姓名、邮箱、电话号码的中间部分会被隐藏。这是为了防止 API 密钥或令牌泄露时联系人被大量收集。合作伙伴已持有原始数据,因此不影响日常使用。
{
"id": "con_af2f95b4_REG-001",
"object": "external_contact",
"event_id": "af2f95b402794c8f9289360fc8765b8e",
"external_id": "REG-001",
"name": "J********h",
"email": "jo**@example.com",
"phone": "010-****-5678",
"company": "Acme Inc.",
"position": "CEO",
"extra": {"booth": "A-12"},
"created_at": "2026-09-07T04:50:38Z",
"updated_at": "2026-09-07T04:50:38Z",
"qr_image_url": "https://cdn.connectelly.com/qr/af2f95b402794c8f9289360fc8765b8e/REG-001.png"
}
| 原始值 | 查询响应 |
|---|---|
| john@example.com | jo**@example.com |
| 010-1234-5678 | 010-****-5678 |
| John Smith | J********h |
| 홍길동 | 홍*동 |
公司名称、职位和 extra 不做脱敏。创建 / 更新(UPSERT)响应会原样返回刚刚发送的值,因此不做脱敏。
响应字段
| 字段 | 类型 | 说明 |
|---|---|---|
| id | string | con_ + event_id 前 8 个字符 + _ + external_id |
| object | string | 固定为 external_contact |
| event_id | string | 活动 ID(32 个字符),与路径中的 eventId 相同 |
| external_id | string | 原样返回发送的 external_id |
| name / email / phone | string | null | 脱敏后的值。未发送则为 null |
| company / position | string | null | 原样返回发送的值。未发送则为 null |
| extra | object | null | 原样返回发送的 extra 对象(例: {"booth": "A-12"})。未发送则为 null |
| created_at | string | 首次登记时间,ISO 8601 (UTC)。之后更新也不会改变 |
| updated_at | string | 最后更新时间,ISO 8601 (UTC) |
| qr_image_url | string | null | 用于打印胸卡的二维码图片 URL(PNG,600×600px)。二维码的值为 external_id 原文。若图片不存在,会在本次查询时生成。无法生成图片时为 null |
错误
| HTTP | code | 原因 |
|---|---|---|
| 404 | contact_not_found | 不存在该 external_id 的联系人 |
| 404 | event_not_found | 活动不存在或超出密钥范围 |
删除联系人
删除 1 条已登记的联系人。适用于取消参观或更正重复登记。
DELETE /api/external/v1/events/{eventId}/contacts/{externalId}
curl -X DELETE https://connectelly.com/api/external/v1/events/<eventId>/contacts/REG-001 \
-H "Authorization: Bearer eyJ..."
响应 204
返回无正文的 204 No Content。
注意
- 删除后再扫描同一二维码,将不会采集线索。
- 已采集的线索不会被删除 — 应用用户收集的名片会保留。
- 此操作无法撤销。如需重新登记,请调用 UPSERT。
- 支持
Idempotency-Key请求头。使用相同的键重试会原样重放第一次的响应 (204);不带键重新发送时,由于联系人已被删除,会返回 404contact_not_found。
错误
| HTTP | code | 原因 |
|---|---|---|
| 404 | contact_not_found | 不存在该 external_id 的联系人 |
| 409 | idempotency_conflict | 相同的 Idempotency-Key 用于不同的请求 |
| 409 | idempotency_in_progress | 使用该 Idempotency-Key 的第一次请求仍在处理中 |
错误码
所有错误均以 Stripe 风格的格式返回。
{
"error": {
"type": "invalid_request_error",
"code": "validation_failed",
"message": "external_id must be 100 characters or less.",
"param": "external_id",
"doc_url": null,
"request_id": "req_3f9a1c2b7d4e5f60"
}
}
message 面向开发者日志,始终为英文,不随语言设置变化。请根据 code 进行错误处理分支,不要依赖 message 的文字。param 是出错的字段名,request_id 用于咨询时追踪请求。
| 代码 | HTTP | 含义 |
|---|---|---|
| validation_failed | 400 | 请求参数校验失败 (缺少必填项、长度超限、条数超限等) |
| invalid_json | 400 | 请求体不是有效的 JSON |
| auth_missing | 401 | 缺少 X-Connectelly-Api-Key 请求头 |
| auth_invalid | 401 | 无法识别密钥 |
| key_expired | 401 | 密钥已过期 (或轮换宽限期已结束) |
| key_revoked | 401 | 密钥已被吊销 |
| token_missing | 401 | 缺少 Authorization: Bearer 令牌 |
| token_invalid | 401 | 令牌无效或已过期 |
| ip_not_allowed | 403 | IP 不在允许列表中 |
| event_not_found | 404 | 活动不存在或超出密钥范围 |
| contact_not_found | 404 | 不存在该 external_id 的联系人 |
| idempotency_conflict | 409 | 相同的 Idempotency-Key 用于不同的请求 (方法、路径或正文) — 请使用新的键 |
| idempotency_in_progress | 409 | 使用该 Idempotency-Key 的第一次请求仍在处理中 — 请稍后重试相同的请求 |
| payload_too_large | 413 | 请求体超过 2MB (带 Idempotency-Key 的请求) — 请拆分后发送 |
| rate_limited | 429 | 超出速率限制 |
| internal_error | 500 | 服务器内部错误 |
速率限制
默认 60 req/min per key per task。Connectelly app-api 会在 2~6 个 task 之间自动扩缩容,因此实际允许量为 120~360 req/min。
所有响应都包含以下响应头:
X-RateLimit-Limit— 每个 task 的允许量X-RateLimit-Remaining— 当前窗口内剩余的请求数Retry-After— 返回 429 时距离重试的等待秒数
幂等性 (Idempotency)
请求超时后,无法确定服务器是否已经处理。请添加 Idempotency-Key 请求头,并使用相同的键重试:请求只会被处理一次,每次重试都会返回第一次的响应。
请求头
| 请求头 | 必填 | 说明 |
|---|---|---|
Idempotency-Key | 否 | 1~255 个可打印 ASCII 字符。推荐 UUID v4。每个新请求请生成新的键 |
- 值为空、超过 255 个字符或包含控制字符时返回 400
validation_failed(param:Idempotency-Key)。 - 中文等非 ASCII 字符可能在到达 API 之前被 403 拒绝。请只使用 ASCII。
- 适用于
POST /contacts、POST /contacts/batch和DELETE /contacts/{externalId}。GET请求和POST /token会忽略此请求头。 - 键按 API Key 区分,不会与其他合作伙伴的键混淆。
- 键自第一次请求起保留 24 小时。之后相同的键将作为新请求处理。
- 带此请求头的请求,请求体超过 2MB 时返回 413
payload_too_large。批量登记上限 500 条在此范围内。
工作方式
| 情况 | 结果 |
|---|---|
| 使用某个键的第一次请求 | 正常处理并保存响应 (见下文) |
| 相同的键,相同的请求 | 不再处理。原样返回第一次响应的状态码和正文,并附带 Idempotent-Replayed: true 响应头 |
| 相同的键,不同的请求 | 409 idempotency_conflict — 请使用新的键 |
| 第一次请求仍在处理时使用相同的键 | 409 idempotency_in_progress — 请稍后重新发送相同的请求 |
- "相同的请求"指方法、路径、查询字符串和正文逐字节相同。仅空格或字段顺序不同也算不同的请求,因此请原样重发第一次发送的字节。
- 重放的正文中
request_id是第一次请求的值,响应头X-Request-Id则是本次重试的新值。 - 重放的响应同样计入速率限制。
HTTP/1.1 200
Content-Type: application/json
Idempotent-Replayed: true
X-Request-Id: req_8b2e4d1f0a9c3e75
会保存的响应
| 响应 | 使用相同的键重试时 |
|---|---|
| 2xx,以及 400、404 等客户端错误 | 已保存 — 重放相同的响应 |
| 5xx、429、408、409、401、403 | 不保存 — 请求会被重新处理 |
由于 400 也会被重放,收到 400 并修正请求后,请使用新的键发送。用旧键发送修正后的正文会返回 409 idempotency_conflict。
示例:超时后重试
每个请求只生成一次键,重试时使用同一个键。下面的 curl 在超时、408、429 和 5xx 时最多使用相同的键重试 3 次。如果重试收到 409 idempotency_in_progress,请稍后重新发送相同的请求。
KEY=$(uuidgen)
curl -X POST https://connectelly.com/api/external/v1/events/<eventId>/contacts \
-H "Authorization: Bearer eyJ..." \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $KEY" \
--max-time 10 --retry 3 \
-d '{ "external_id": "REG-001", "name": "John Smith" }'
更新日志
- 2026-10-14 — 支持
Idempotency-Key(联系人登记 / 批量 / 删除),新增联系人列表 API 文档,补充联系人查询响应字段,extra改为以对象而非字符串返回 - v1.0 (2026-09) — 首次发布: Contacts API (单条 / 批量 / 查询 / 删除)