API Documentation

Everything you need to connect your registration system to Connectelly.

Quickstart

The Connectelly External API uses two-step authentication. Exchange your API key for a short-lived access token, then call the API with that token.

  1. Apply as a partner and get an API key β€” issued by a Connectelly master after your contract is signed.
  2. Get an access token β€” POST /api/external/v1/token
  3. Call with the token β€” Authorization: Bearer <access_token>
# 1) Exchange the key for a token (the key is used only here)
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..."}   ← this is your eventId

# 2) Send contacts with the token
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","email":"john@example.com"}'

200 means stored. When an app user scans REG-001's QR, a lead is auto-captured.

Authentication

Two-step authentication

Your long-lived API key is used only to obtain a token; all API calls use a short-lived access token. The key never travels with normal traffic, so it cannot leak through partner logs or proxies.

API key

  • Format: ck_live_<32 url-safe base64>
  • Sent as X-Connectelly-Api-Key only on POST /api/external/v1/token
  • Scoped to a single event. Using it for another event returns 404 event_not_found.
  • Server-side only. Never embed in client code or commit to public repos.

Access token

  • Lives 30 minutes (1800s). Request a new one when it expires.
  • Send as Authorization: Bearer <access_token> on every call.
  • If the key is revoked, already-issued tokens stop working immediately.

If your key leaks

Tell your Connectelly administrator to revoke it. Revocation instantly blocks the key and every token issued from it. When a replacement key is issued, the old one stays valid for a 24-hour grace period, so you can rotate without downtime.

IP restrictions (recommended)

Register allowed IPs or CIDR ranges and tokens can only be issued from those addresses β€” a leaked key is unusable elsewhere. Ask your administrator to configure this.

Token API

Exchange your API key for a short-lived access token. This is the only endpoint that accepts the API key; every other endpoint uses the token you get here.

Request

POST /api/external/v1/token

HeaderRequiredDescription
X-Connectelly-Api-KeyYesYour issued API key (ck_live_...)

No request body is required.

curl -X POST https://connectelly.com/api/external/v1/token \
  -H "X-Connectelly-Api-Key: ck_live_..."

Response 200

{
  "access_token": "eyJhbGciOiJIUzM4NCJ9...",
  "token_type": "Bearer",
  "expires_in": 1800,
  "expires_at": "2026-09-07T05:18:35Z",
  "event_id": "af2f95b402794c8f9289360fc8765b8e"
}
FieldTypeDescription
access_tokenstringUse in the Authorization header of every later request
token_typestringAlways Bearer
expires_inintegerSeconds remaining. Default 1800 (30 minutes)
expires_atstringExpiry timestamp (ISO-8601 UTC)
event_idstringThe event this key belongs to. Use it for {eventId} in every other endpoint

Where does eventId come from?

Each API key is bound to a single event, so you never have to track eventId yourself. You can obtain it two ways:

  • event_id in the token response β€” returned above. Prefer reading it from here rather than hardcoding.
  • From your administrator β€” provided together with the API key.

Using another event's eventId returns 404 event_not_found.

Errors

HTTPcodeCause
401auth_missingHeader not provided
401auth_invalidKey not recognized
401key_expiredKey expired (or rotation grace ended)
401key_revokedKey was revoked
403ip_not_allowedRequest came from a non-allowlisted IP

Operational tips

  • Cache the token instead of requesting one per call. Refresh it a minute or two before it expires.
  • On a 401 token_invalid, fetch a new token and retry once.
  • Keep tokens in server memory only β€” never in logs or URLs.

Create / Update contact

Creates a single contact, or overwrites it if the external_id already exists (UPSERT). Call it at registration time so a lead is auto-captured when the app scans the QR.

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

Request headers

HeaderRequiredDescription
AuthorizationYesBearer <access_token>
Content-TypeYesapplication/json
Idempotency-KeyNoPrevents duplicates on retry. See Idempotency

Request body

FieldTypeRequiredDescription
external_idstringYesYour system's unique ID, max 100 chars. Must match the QR value
namestringNoFull name, max 200 chars
emailstringNoEmail. Encrypted at rest (AES-256-GCM)
phonestringNoPhone number. Encrypted at rest
companystringNoCompany name, max 200 chars
positionstringNoJob title, max 100 chars
extraobjectNoFree-form JSON for your own fields
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": "+1-555-000-0000",
    "company": "Acme Inc.",
    "position": "CEO",
    "extra": { "booth": "A-12" }
  }'

Response 200

Echoes back exactly what you sent, so you can confirm the write.

{
  "id": "con_af2f95b4_REG-001",
  "object": "external_contact",
  "event_id": "af2f95b4...",
  "external_id": "REG-001",
  "name": "John Smith",
  "email": "john@example.com",
  "phone": "+1-555-000-0000",
  "company": "Acme Inc.",
  "position": "CEO",
  "extra": { "booth": "A-12" },
  "created_at": "2026-09-07T04:50:38Z",
  "updated_at": "2026-09-07T04:50:38Z"
}

Errors

HTTPcodeCause
400validation_failedexternal_id missing or too long
401token_missing / token_invalidNo token / forged or expired
404event_not_foundEvent missing or outside this key's scope
409idempotency_conflictSame Idempotency-Key, different body
429rate_limitedRate limit exceeded

Batch upsert

Upserts up to 500 contacts in one call. Use it for initial loads or nightly syncs.

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

Request body

FieldTypeRequiredDescription
contactsarrayYes1–500 contacts, each in the single-contact format
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" },
      { "external_id": "REG-002", "name": "Jane Doe", "email": "jane@example.com" }
    ]
  }'

Response 200

{
  "created": 2,
  "updated": 0,
  "contacts": [ { "id": "con_...", ... }, { "id": "con_...", ... } ]
}
FieldTypeDescription
createdintegerNewly created contacts
updatedintegerExisting contacts overwritten
contactsarrayAll processed contacts

Notes

  • An empty array or more than 500 items returns 400 validation_failed.
  • The batch is all-or-nothing β€” a partial batch is never stored.
  • For large loads, pace your calls; see Rate Limits to avoid 429.

Retrieve contact

Fetches a single stored contact.

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

Response 200 β€” personal data is masked

Name, email and phone come back with the middle characters hidden. This limits the damage if an API key or token leaks: the data cannot be harvested in bulk. You already hold the originals you sent, so day-to-day use is unaffected.

{
  "id": "con_af2f95b4_REG-001",
  "object": "external_contact",
  "external_id": "REG-001",
  "name": "J********h",
  "email": "jo**@example.com",
  "phone": "+1-5**-***-0000",
  "company": "Acme Inc.",
  "position": "CEO",
  "created_at": "2026-09-07T04:50:38Z",
  "updated_at": "2026-09-07T04:50:38Z"
}
StoredReturned by GET
john@example.comjo**@example.com
010-1234-5678010-****-5678
John SmithJ********h

Company, position and extra are not masked. Upsert responses are never masked β€” they echo what you just sent.

Errors

HTTPcodeCause
404contact_not_foundNo contact with that external_id
404event_not_foundEvent missing or outside this key's scope

Delete contact

Removes a stored contact β€” use it for cancellations or to correct a duplicate registration.

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..."

Response 204

Returns 204 No Content with an empty body.

Notes

  • After deletion, scanning that QR no longer captures a lead.
  • Leads already captured are not deleted β€” cards collected by app users are kept.
  • This cannot be undone. Call the upsert endpoint to add the contact back.

Errors

HTTPcodeCause
404contact_not_foundNo contact with that external_id

Error Codes

All errors follow the Stripe-style format.

{
  "error": {
    "type": "invalid_request_error",
    "code": "external_id_too_long",
    "message": "external_id must be 100 characters or less.",
    "param": "external_id",
    "doc_url": "...",
    "request_id": "req_..."
  }
}
CodeHTTPMeaning
auth_missing401Header missing
token_missing401Bearer token missing
token_invalid401Token invalid or expired
ip_not_allowed403IP not permitted
auth_invalid401Key not recognized
key_expired401Key expired
key_revoked401Key revoked
event_not_found404Event missing or out of scope
contact_not_found404Contact missing
validation_failed400Invalid body
idempotency_conflict409Same key, different body
rate_limited429Rate limit exceeded
internal_error500Server error

Rate Limits

Default 60 req/min per key per task. Connectelly app-api runs 2~6 tasks (auto-scaled), so effective allowance is 120~360 req/min.

Every response includes:

  • X-RateLimit-Limit β€” per-task allowance
  • X-RateLimit-Remaining β€” remaining requests in current window
  • Retry-After β€” seconds until retry on 429

Idempotency

To avoid duplicate processing on network retries, send an Idempotency-Key header.

  • Up to 100 chars (UUID recommended)
  • Cached for 24 hours β€” retries return the stored response
  • Same key with different body returns 409 idempotency_conflict

Changelog

  • v1.0 (2026-09) β€” Initial release: Contacts API (single / batch / retrieve / delete)