Audience & broadcasts
Contacts
Store the people you email, organize them with segments, topics and properties, and manage them in the dashboard or over the API.
A contact is one email address in a project, with an optional name, custom properties, segment memberships and topic preferences. You send marketing email to contacts through broadcasts.
Contacts belong to a project. Two projects in the same team can each hold a contact with the same email address.
Create a contact
In the dashboard, open Audience, choose Contacts and add an email address with an optional first and last name. With the API, use a full_access key:
curl -X POST https://api.sooner.dev/contacts \
-H "Authorization: Bearer $SOONER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"email": "ada@example.com",
"first_name": "Ada",
"last_name": "Lovelace",
"properties": { "company": "Acme" },
"segments": [{ "id": "jd5f2h8k4m7p1r6t9v3x0z2c" }],
"topics": [{ "id": "jt3b6n9q2w5e8r1y4u7i0o2p", "subscription": "opt_in" }]
}'{ "object": "contact", "id": "jx7r4k9m2c5v8n1b3q6w0e2t" }Only email is required. See the contacts API reference for every field.
- The address is stored in lowercase. A project can hold one contact per address, and creating a duplicate returns
422 validation_error. - Every key in
propertiesmust be declared first. See Contact properties. - Every segment and topic ID must exist in the project, or the request returns
404 not_found.
Find, update and delete a contact
The {identifier} in contact paths is either the contact ID or the email address. Both work everywhere.
curl https://api.sooner.dev/contacts/ada@example.com \
-H "Authorization: Bearer $SOONER_API_KEY"{
"object": "contact",
"id": "jx7r4k9m2c5v8n1b3q6w0e2t",
"email": "ada@example.com",
"first_name": "Ada",
"last_name": "Lovelace",
"unsubscribed": false,
"properties": { "company": "Acme" },
"created_at": "2026-11-14T10:30:00.000Z"
}An update changes only the fields you send. Properties are merged into the existing ones, so you can update one property without resending the rest. Send null for first_name, last_name or a property to clear it.
curl -X PATCH https://api.sooner.dev/contacts/ada@example.com \
-H "Authorization: Bearer $SOONER_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "properties": { "company": "Analytical Engines" } }'Deleting a contact removes its segment memberships and topic preferences and frees one slot in your contact limit.
Unsubscribed contacts
unsubscribed is the contact's global opt-out. A contact with unsubscribed: true is skipped by every broadcast. Contacts set it themselves through the unsubscribe link in your emails, and you can set it yourself with the API. See Unsubscribes.
Setting unsubscribed back to false makes the contact eligible again. Only do this when the person has asked to receive your email again.
Segments and topics
A contact can be in any number of segments, and can opt in to or out of any number of topics. Manage both from the contact, or from the segment and topic side:
| Task | Endpoint |
|---|---|
| Add to a segment | POST /contacts/{identifier}/segments/{segmentId} |
| Remove from a segment | DELETE /contacts/{identifier}/segments/{segmentId} |
| List a contact's segments | GET /contacts/{identifier}/segments |
| Set topic preferences | PATCH /contacts/{identifier}/topics |
| List a contact's topics | GET /contacts/{identifier}/topics |
Contact limits
The limit applies to the whole team, across all of its projects.
| Plan | Contacts |
|---|---|
| Free | 2,000 |
| Pro | 25,000 |
| Scale | 150,000 |
| Enterprise | Unlimited |
When you reach the limit, creating a contact returns 422 validation_error with the message "Your plan allows 2,000 contacts. Upgrade to add more." Deleting contacts frees space immediately. Check your current count with GET /usage.
Webhooks
Creating, updating and deleting a contact fires contact.created, contact.updated and contact.deleted. See Event types.
Importing contacts
Sooner has no file import. To load an existing list, create contacts in a loop, staying under your plan's request rate limit:
const contacts = [
{ email: "ada@example.com", first_name: "Ada" },
{ email: "grace@example.com", first_name: "Grace" },
];
for (const contact of contacts) {
const res = await fetch("https://api.sooner.dev/contacts", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SOONER_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ ...contact, segments: [{ id: "jd5f2h8k4m7p1r6t9v3x0z2c" }] }),
});
if (!res.ok) console.error(contact.email, await res.json());
}