FastLinkIt

Bulk APIs and subscribe widget

API & Integrationscontactssubscribeapi5 min read

Available on Starter plan and above (subscribe widget is free); bulk APIs require Starter+ for contacts scope

Ingest contacts at scale, sync tags from an external system, and embed a hosted subscribe form on any third-party site.

Bulk contact upsert

POST /api/contacts/bulk — designed for nightly syncs from a CRM, one-time migrations from another tool, and batch-imports of trade-show badge scans.

Request shape

POST /api/contacts/bulk
X-Api-Key: fli_...
Content-Type: application/json

{
  "contacts": [
    { "email": "alice@example.com", "firstName": "Alice", "company": "Acme" },
    { "email": "bob@example.com", "firstName": "Bob", "jobTitle": "CTO" }
  ],
  "tags": ["import:2026-q1", "newsletter"]
}

Behaviour

  • Upsert by email — existing contacts are updated, new ones are created.
  • Null fields = leave alone — partial updates from a CRM nightly sync don't accidentally clear fields that weren't pushed.
  • Top-level tags — applied platform-wide to every row in the batch (created or updated). Useful for stamping the whole import.
  • Hard cap 10,000 contacts per request — beyond that, the request pipeline starts to time out under realistic SQL load. Larger imports should chunk client-side.
  • 500-row chunks internally — EF's change tracker stays bounded.
  • One pre-load query — existing contacts are fetched in a single WHERE Email IN (...) trip.

Response

{
  "totalSubmitted": 2,
  "created": 1,
  "updated": 1,
  "skipped": 0,
  "errors": []
}

Per-row failures (validation errors etc.) appear in errors; the rest of the batch still processes.

Bulk tag add/remove

PATCH /api/contacts/bulk/tags — add or remove tags across many contacts in one call.

PATCH /api/contacts/bulk/tags
X-Api-Key: fli_...
Content-Type: application/json

{
  "contactIds": ["...", "..."],
  "emails": ["alice@example.com"],
  "addTags": ["vip"],
  "removeTags": ["churn-risk"]
}
  • Match contacts by contactIds and / or emails (results unioned, owner-filtered server-side).
  • addTags and / or removeTags — at least one must be non-empty.
  • The add path uses EnsureTagsAsync (find-or-create the tag rows in one batch) + pre-filtered inserts to avoid noisy unique-constraint exceptions.
  • The remove path uses ExecuteDeleteAsync — one DELETE per tag, not per row.

Response:

{ "matched": 247, "tagsAdded": 247, "tagsRemoved": 12 }

Subscribe widget

A hosted JavaScript widget you drop on any third-party site to collect newsletter signups directly into a contact group. No CSS conflicts, no build step.

Embed

<script src="https://flnk.it/js/subscribe-widget.js"
        data-group="GROUP_ID"
        data-theme="light"
        data-show-name="true"
        data-button-text="Subscribe"
        data-success-message="Thanks for subscribing!"></script>

Data attributes

Attribute Default Description
data-group required The contact-group GUID — get it from /contacts/groups/{id} URL bar
data-theme light light or dark
data-show-name false Add an optional name field
data-button-text Subscribe Button label
data-success-message Thanks for subscribing! Shown after a successful submit
data-email-placeholder your@email.com Email input placeholder
data-name-placeholder Your name Name input placeholder
data-mount (auto) CSS selector for an explicit mount point — defaults to a <div> inserted right after the script tag
data-api https://flnk.it Override for self-hosted FastLinkIt deploys

How it works

  1. The widget reads its config from data-attrs on its own <script> tag.
  2. Renders a self-contained form with inline styles (won't fight host CSS).
  3. On submit, POSTs to /api/subscribe with Content-Type: text/plain so the request stays a CORS "simple request" — no preflight needed.
  4. Server side: IContactService.UpsertFromSourceAsync upserts the contact with a source:subscribe:<groupName> tag, fires the contact.created webhook for new arrivals, and adds the contact to the group (idempotent).
  5. Server validates the email shape and rate-limits 1 signup per (groupId, email) per 60 seconds.
  6. On success, the form swaps for a check-mark message; on error, shows the message inline and re-enables submit.

Where the GROUP_ID comes from

Go to /contacts/groups, click the group you want signups to land in. The URL is /contacts/groups/{id} — that GUID is what you put in data-group. Group GUIDs are 32 bytes of entropy — non-enumerable and safe to be public.

Activity API

GET /api/contacts/{id}/activity?skip=0&take=50 — returns the unified timeline aggregating mailings, payments, bookings, donations, tickets, and inbox messages for one contact. Same shape as the contact 360° UI.

Per-tenant authorisation: refuses if the contact doesn't belong to the caller (otherwise a guessed contact id could enumerate another tenant's history).

Worked example — nightly CRM sync

Goal: a Salesforce nightly export pushes 5,000 updated contacts into FastLinkIt at 2 AM.

  1. Set up the export job in Salesforce.
  2. Generate a FastLinkIt API key with contacts and mailing scopes.
  3. The export hits POST /api/contacts/bulk with tags: ["crm-sync:2026-05-14"].
  4. Response confirms 4,800 updated, 200 created, 0 errors.
  5. A morning report can run GET /api/contacts/bulk/tags (PATCH for tag add) to set a crm-sync:current tag on the imported batch and remove it from yesterday's batch.
  6. Smart segments built on the crm-sync:current tag pick up exactly today's import.

Rejoining the server...

Rejoin failed... trying again in seconds.

Failed to rejoin.
Please retry or reload the page.

The session has been paused by the server.

Failed to resume the session.
Please retry or reload the page.