Skip to navigation

Add contacts in bulk

View as Markdown

Adds 1 to 5,000 contacts to a campaign that is not completed/archived, in one all-or-nothing insert. Every item follows CreateContactInput, and one invalid item rejects the whole request with 400. Item errors are grouped under details.fieldErrors.contacts without naming the index, so validate items on your side first. New contacts are created pending.

Deduplication.

  • Items with the same phone key are collapsed and the first one wins. Email-only items are keyed by their address.
  • Phones that already exist in the campaign are skipped, and their stored data is not updated (unlike the single-contact endpoint).
  • Phones removed from this campaign earlier are restored as pending. imported counts only newly inserted rows.

Unlike the single endpoint, email-only items are not matched against contacts in other campaigns. As with it, contacts added to an active campaign are not queued until the campaign is paused and activated again, and DNC lists and opt-outs are enforced at send time, not here.

Side effects. Inserts the contacts, records their consent provenance (import:api_bulk, no evidence) and writes an audit entry. Nobody is contacted.

Idempotency. Safe to retry: a repeated batch imports only phones that are not already in the campaign, so a retry after a timeout returns imported: 0 for rows that landed the first time.

Webhook events. contact.created for each new contact row ({ contact_id, campaign_id, name, phone_number, email, whatsapp_number, status, source, created_at }, with source api:bulk); not sent when an existing contact is returned or a removed contact is restored; one event per inserted row. Delivered only to webhooks that name contact.created (it is opt-in only: the "*" wildcard does not include it). Also audit.log_recorded for webhooks subscribed to it. See Webhooks.

Access

  • Required scope: write (or full).
  • Rate limit: Contacts import — 5 requests/min per workspace, shared only by /bulk, /upload-preview and /upload-confirm, on top of the general API limit. See Rate limits.
  • Plan: The whole batch counts against the workspace-wide contact cap (Starter 500, Growth 2,000, Business 20,000). A batch that would go over it is refused entirely with 403 BILLING_ERROR; send a smaller batch.

Authentication

AuthorizationBearer

Workspace API key: jl_ followed by 64 lowercase hex characters, created by the workspace owner in the dashboard (Settings → API Keys) and sent as Authorization: Bearer jl_.... The plaintext is shown once, at creation; Jelliu stores only a SHA-256 hash. A workspace can hold up to 25 active keys.

ScopeGET / HEADPOST / PUT / PATCH / DELETEAdmin-only routes
readYesNoNo
writeYesYesNo
fullYesYesYes

Operations restricted to admins or owners reject keys without the full scope with 403, and say so in their description. No key, whatever its scope, can mint or revoke API keys or rotate a webhook secret — that requires a signed-in owner session. A revoked key stops authenticating within about 10 seconds. See Authentication.

Path parameters

campaignIdstringRequiredformat: "uuid"

UUID of the campaign that receives the contacts. An unknown, deleted or foreign campaign answers 404; a value that is not a UUID answers 400.

Request

This endpoint expects an object.
contactslist of objectsRequired

1 to 5,000 contacts. Each needs at least one of phoneNumber, email or whatsappNumber.

Response

The batch was processed.
dataobject

Errors

400
Bad Request Error
401
Unauthorized Error
403
Forbidden Error
404
Not Found Error
429
Too Many Requests Error