API 文档

将您的登记系统与 Connectelly 对接所需的一切。

快速入门

Connectelly External API 采用两步认证。先用 API 密钥换取短期有效的 access token,再使用该令牌调用 API。

  1. 申请成为合作伙伴并获取 API 密钥 — 签约完成后由主管理员发放密钥。
  2. 获取 access token — POST /api/external/v1/token
  3. 使用令牌调用 — 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_tokenstring用于之后所有请求的 Authorization 请求头
token_typestring固定为 Bearer
expires_ininteger剩余有效时间(秒)。默认 1800 = 30 分钟
expires_atstring过期时间 (ISO-8601 UTC)
event_idstring该密钥绑定的活动 ID。直接用于之后所有端点路径中的 {eventId}

eventId 从哪里获取?

API 密钥绑定单个活动发放,因此无需单独管理 eventId。可以通过以下两种方式确认:

  • 令牌响应中的 event_id — 直接从上面的响应中获取。建议使用此值,而不要硬编码。
  • 发放密钥时告知 — 负责人交付 API 密钥时会一并告知。

传入其他活动的 eventId 会返回 404 event_not_found。

错误

HTTPcode原因
401auth_missing缺少请求头
401auth_invalid无法识别密钥
401key_expired密钥已过期 (或轮换宽限期已结束)
401key_revoked密钥已被吊销
403ip_not_allowedIP 不在允许列表中

运维建议

  • 不要每次请求都获取令牌,请缓存令牌。建议在过期前 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_idstring是登记系统中的唯一 ID,最多 100 个字符。必须与二维码的值一致
namestring否姓名,最多 200 个字符
emailstring否邮箱。保存时使用 AES-256-GCM 加密
phonestring否电话号码。保存时加密
companystring否公司名称,最多 200 个字符
positionstring否职位,最多 100 个字符
extraobject否自由格式 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
}

错误

HTTPcode原因
400validation_failed缺少 external_id 或字段长度超限
400invalid_json请求体不是有效的 JSON
401token_missing / token_invalid缺少令牌 / 令牌伪造或过期
404event_not_found活动不存在或超出密钥范围
409idempotency_conflict相同的 Idempotency-Key 用于不同的请求
409idempotency_in_progress使用该 Idempotency-Key 的第一次请求仍在处理中
429rate_limited超出速率限制

批量导入

一次最多 UPSERT 500 条。适用于初始数据导入或夜间同步。

POST /api/external/v1/events/{eventId}/contacts/batch

请求 body

字段类型必填说明
contactsarray是联系人数组,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"
    }
  ]
}
字段类型说明
totalinteger请求中的总条数
createdinteger新建条数
updatedinteger覆盖已有数据的条数
failedinteger未保存的条数
errorsarray仅包含失败项 (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"
    }
  ]
}
字段类型说明
totalinteger该活动下保存的联系人总数
returnedinteger本次响应中包含的联系人数(最多 10,000)
truncatedboolean为 true 时表示达到 10,000 条上限,仅返回了最新的 10,000 条。遇到此情况请联系我们
contactsarray联系人列表,按登记时间从新到旧排列
  • 按首次登记时间(created_at)从新到旧排序。更新(UPSERT)不会改变顺序。
  • 列表查询不会生成新的二维码图片。qr_image_url 是登记时图片的保存地址,若登记时生成失败,该链接可能无法打开。用查询联系人 API 查询一次该联系人即可生成图片。

错误

HTTPcode原因
404event_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.comjo**@example.com
010-1234-5678010-****-5678
John SmithJ********h
홍길동홍*동

公司名称、职位和 extra 不做脱敏。创建 / 更新(UPSERT)响应会原样返回刚刚发送的值,因此不做脱敏。

响应字段

字段类型说明
idstringcon_ + event_id 前 8 个字符 + _ + external_id
objectstring固定为 external_contact
event_idstring活动 ID(32 个字符),与路径中的 eventId 相同
external_idstring原样返回发送的 external_id
name / email / phonestring | null脱敏后的值。未发送则为 null
company / positionstring | null原样返回发送的值。未发送则为 null
extraobject | null原样返回发送的 extra 对象(例: {"booth": "A-12"})。未发送则为 null
created_atstring首次登记时间,ISO 8601 (UTC)。之后更新也不会改变
updated_atstring最后更新时间,ISO 8601 (UTC)
qr_image_urlstring | null用于打印胸卡的二维码图片 URL(PNG,600×600px)。二维码的值为 external_id 原文。若图片不存在,会在本次查询时生成。无法生成图片时为 null

错误

HTTPcode原因
404contact_not_found不存在该 external_id 的联系人
404event_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);不带键重新发送时,由于联系人已被删除,会返回 404 contact_not_found。

错误

HTTPcode原因
404contact_not_found不存在该 external_id 的联系人
409idempotency_conflict相同的 Idempotency-Key 用于不同的请求
409idempotency_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_failed400请求参数校验失败 (缺少必填项、长度超限、条数超限等)
invalid_json400请求体不是有效的 JSON
auth_missing401缺少 X-Connectelly-Api-Key 请求头
auth_invalid401无法识别密钥
key_expired401密钥已过期 (或轮换宽限期已结束)
key_revoked401密钥已被吊销
token_missing401缺少 Authorization: Bearer 令牌
token_invalid401令牌无效或已过期
ip_not_allowed403IP 不在允许列表中
event_not_found404活动不存在或超出密钥范围
contact_not_found404不存在该 external_id 的联系人
idempotency_conflict409相同的 Idempotency-Key 用于不同的请求 (方法、路径或正文) — 请使用新的键
idempotency_in_progress409使用该 Idempotency-Key 的第一次请求仍在处理中 — 请稍后重试相同的请求
payload_too_large413请求体超过 2MB (带 Idempotency-Key 的请求) — 请拆分后发送
rate_limited429超出速率限制
internal_error500服务器内部错误

速率限制

默认 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 (单条 / 批量 / 查询 / 删除)