API 문서

등록 시스템과 Connectelly 를 연동하는 데 필요한 모든 것.

빠른 시작

Connectelly External API 는 2단계 인증을 사용합니다. API Key 로 단수명 access token 을 발급받고, 실제 호출은 그 토큰으로 합니다.

  1. 파트너 신청 후 API Key 받기 — 계약 완료 후 마스터가 키를 발급합니다.
  2. access token 발급 — POST /api/external/v1/token
  3. 토큰으로 호출 — Authorization: Bearer <access_token>
# 1) 토큰 발급 (API Key 는 여기서만 사용)
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 QR 을 스캔하면 리드가 자동 캡처됩니다.

인증

2단계 인증

장수명 API Key 는 토큰 발급에만 쓰고, 실제 API 호출은 단수명 access token 으로 합니다. 평상시 트래픽에 키가 실리지 않아 파트너 서버 로그·프록시를 통한 노출이 사라집니다.

API Key

  • 형식: 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 Key 를 단수명 access token 으로 교환합니다. API Key 를 헤더에 담는 엔드포인트는 이곳뿐이며, 나머지 모든 API 는 여기서 받은 토큰으로 호출합니다.

요청

POST /api/external/v1/token

헤더필수설명
X-Connectelly-Api-Key필수발급받은 API Key (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 Key 는 행사 1개에 묶여 발급되므로 eventId 를 따로 관리할 필요가 없습니다. 두 가지 방법으로 확인할 수 있습니다.

  • 토큰 응답의 event_id — 위 응답에서 바로 얻습니다. 하드코딩하지 말고 이 값을 쓰는 것을 권장합니다.
  • 키 발급 시 안내 — 담당자가 API Key 를 전달할 때 함께 알려드립니다.

다른 행사의 eventId 를 넣으면 404 event_not_found 가 반환됩니다.

오류

HTTPcode원인
401auth_missing헤더 없음
401auth_invalid키가 인식되지 않음
401key_expired키 만료 (또는 재발급 유예 종료)
401key_revoked키가 폐기됨
403ip_not_allowed허용되지 않은 IP

운영 팁

  • 매 요청마다 발급하지 말고 토큰을 캐시하세요. 만료 1~2분 전에 갱신하는 것을 권장합니다.
  • 401 token_invalid 를 받으면 토큰을 재발급받아 1회만 재시도하세요.
  • 토큰은 서버 메모리에만 두고 로그·URL 에 남기지 마세요.

연락처 등록·수정

연락처 1건을 등록하거나 수정합니다. external_id 가 이미 있으면 덮어씁니다(UPSERT). 등록 시점에 호출하면 앱 QR 스캔 시 리드가 자동 캡처됩니다.

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

요청 헤더

헤더필수설명
Authorization필수Bearer <access_token>
Content-Type필수application/json
Idempotency-Key선택같은 키로 재시도하면 다시 처리하지 않고 첫 응답을 돌려줍니다. 멱등성 항목 참조

요청 body

필드타입필수설명
external_idstring필수등록 시스템의 고유 ID. 최대 100자. QR 값과 일치해야 합니다
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 입니다 — QR 이미지 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_failedexternal_id 누락 또는 필드 길이 초과
400invalid_json요청 본문이 올바른 JSON 이 아님
401token_missing / token_invalid토큰 없음 / 위조·만료
404event_not_found행사 없음 또는 키 범위 밖
409idempotency_conflict같은 Idempotency-Key 를 다른 요청에 사용
409idempotency_in_progress같은 Idempotency-Key 의 첫 요청이 아직 처리 중
429rate_limited속도 제한 초과

연락처 일괄 등록

최대 500건을 한 번에 UPSERT 합니다. 초기 데이터 적재나 야간 동기화에 사용하세요.

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 헤더를 지원합니다. 타임아웃 시 같은 키로 재시도하면 한 번만 처리됩니다. 멱등성 항목 참조.

연락처 목록

이 행사에 저장된 연락처 전체를 한 번에 조회합니다. 보낸 데이터를 대사하거나, 명찰 인쇄용 QR 이미지를 일괄로 모을 때 씁니다.

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)
truncatedbooleantrue 면 10,000건 상한에 걸려 최근 10,000건만 반환된 것입니다. 이 경우 담당자에게 문의하세요
contactsarray연락처 목록. 최근 등록순
  • 정렬은 최초 등록 시각(created_at) 기준 최신순입니다. 수정(UPSERT)해도 순서는 바뀌지 않습니다.
  • 목록 조회는 QR 이미지를 새로 만들지 않습니다. qr_image_url 은 등록 시 이미지가 저장되는 주소이므로, 등록 당시 생성에 실패한 건은 링크가 열리지 않을 수 있습니다. 그 연락처를 연락처 조회 API 로 한 번 조회하면 이미지가 만들어집니다.

오류

HTTPcode원인
404event_not_found행사 없음 또는 키 범위 밖

연락처 조회

등록된 연락처 1건을 조회합니다.

GET /api/external/v1/events/{eventId}/contacts/{externalId}

응답 200 — 개인정보는 마스킹됩니다

조회 응답의 이름·이메일·전화번호는 가운데가 가려집니다. API Key 나 토큰이 유출됐을 때 연락처가 대량으로 수집되는 것을 막기 위한 조치입니다. 파트너는 원본을 이미 보유하고 있으므로 실무에는 지장이 없습니다.

{
  "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명찰 인쇄용 QR 이미지 URL (PNG, 600×600px). QR 값은 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 를 반환합니다.

주의

  • 삭제 후 같은 QR 을 스캔하면 리드가 캡처되지 않습니다.
  • 이미 캡처된 리드는 삭제되지 않습니다 — 앱 유저가 수집한 명함은 그대로 유지됩니다.
  • 되돌릴 수 없습니다. 다시 등록하려면 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_missing401X-Connectelly-Api-Key 헤더 없음
auth_invalid401키 인식 불가
key_expired401키 만료 (또는 재발급 유예 종료)
key_revoked401키 폐기됨
token_missing401Authorization: Bearer 토큰 없음
token_invalid401토큰 위조 또는 만료
ip_not_allowed403허용되지 않은 IP
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선택출력 가능한 ASCII 1~255자. 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 (단건/일괄/조회/삭제)