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