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.** `audit.log_recorded` for webhooks subscribed to it. See [Webhooks](/webhooks). **Access** - **Required scope:** `write` (or `full`). - **Rate limit:** Contacts import — 5 requests/min per workspace, shared by every route under `/api/campaigns/{campaignId}/contacts`, on top of the general API limit. See [Rate limits](/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. | Scope | GET / HEAD | POST / PUT / PATCH / DELETE | Admin-only routes | | --- | --- | --- | --- | | `read` | Yes | No | No | | `write` | Yes | Yes | No | | `full` | Yes | Yes | Yes | 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](/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