RefreshDocsConsole →

API reference

Contacts

Upsert, read and delete contacts; set per-topic subscriptions.

Contacts belong to the organization, and all its domains share them. The platform keys contacts by email address. You create topics in the console (Audience → Topics). The API refers to them by slug.

Upsert a contact

POST /api/v1/contacts
Field Type Notes
email string Required. Lower-cased and trimmed.
first_name, last_name string | null Optional; ≤ 120 chars. Only changed when present.
properties object Optional. Scalar values only (string ≤ 1000, number, boolean); null deletes a key. Keys [A-Za-z0-9_.-], ≤ 100 keys. Merged into existing properties.
status string subscribed | unsubscribed | bounced. Optional.
topics string[] Optional, ≤ 50 topic slugs to subscribe on this upsert. A missing slug that matches the topic slug rule is created (name = slug, default opt-in on). An invalid slug → 422 unknown_topic, nothing written.

The API rejects unknown fields (422 validation_error). SEND keys may call this endpoint, including domain-restricted keys. When the call creates a new contact, it fires contact.created automations unless the key is domain-restricted.

curl -X POST https://api.f5send.com/api/v1/contacts \
  -H "Authorization: Bearer $F5SEND_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "email": "jane@example.org", "first_name": "Jane", "properties": { "plan": "pro" }, "topics": ["newsletter"] }'

Response — the contact object plus created:

{
  "object": "contact",
  "id": "cmg0k2…",
  "email": "jane@example.org",
  "first_name": "Jane",
  "last_name": null,
  "status": "subscribed",
  "properties": { "plan": "pro" },
  "subscriptions": [
    { "topic": "newsletter", "state": "subscribed", "explicit": true, "changed_at": "2026-08-17T14:03:11.120Z", "source": "api:cmf…" },
    { "topic": "product-updates", "state": "subscribed", "explicit": false, "changed_at": null, "source": null }
  ],
  "created_at": "2026-08-17T14:03:11.120Z",
  "updated_at": "2026-08-17T14:03:11.120Z",
  "created": true
}

subscriptions lists every topic of the organization with the effective state of the contact. The state is the explicit row when one exists (explicit: true), otherwise the default opt-in of the topic.

Retrieve a contact

GET /api/v1/contacts/:id

:id is a contact id or an email address (URL-encoded). The API returns 404 not_found when the contact is not in this organization.

Set subscriptions

POST /api/v1/contacts/:id/subscriptions

The body is one change or a list of up to 50 changes:

{ "topic": "newsletter", "state": "unsubscribed" }
{ "subscriptions": [{ "topic": "newsletter", "state": "subscribed" }, { "topic": "digest", "state": "unsubscribed" }] }

state is subscribed | unsubscribed. The call returns the contact object. When a contact subscribes to a topic, the call fires topic.subscribed automations for that topic.

Delete a contact

DELETE /api/v1/contacts/:id

FULL keys only (403 forbidden otherwise), and only f5_live_ keys without a domain restriction (403 test_key_not_allowed, 403 forbidden_domain). The call removes the contact and its subscription rows. Suppressions are separate and remain. Response: { "object": "contact", "id": "…", "deleted": true }.