Audiences and contacts
An audience is a list of contacts you can send broadcasts to. A contact belongs to exactly one audience; the same email address in two audiences is two contacts, each with its own unsubscribe state.
Audiences
Section titled “Audiences”curl -X POST https://api.spacepost.dev/audiences \ -H "Authorization: Bearer $SPACEPOST_API_KEY" -H "Content-Type: application/json" \ -d '{ "name": "Newsletter" }'{ "object": "audience", "id": "0192e1c4-0000-7000-8000-0000000000a1", "name": "Newsletter" }const { data } = await spacepost.audiences.create({ name: "Newsletter" });GET /audiences, GET /audiences/{id} and DELETE /audiences/{id} complete the set. Deleting an audience deletes its contacts. Audience names are 1 to 100 characters.
Contacts
Section titled “Contacts”Contacts live under their audience: /audiences/{audienceId}/contacts.
emailstringrequiredUnique within the audience. Stored lower-cased. Creating a duplicate returns 422.
first_namestringoptionalUp to 100 characters. Used by {{{FIRST_NAME}}}.
last_namestringoptionalUp to 100 characters. Used by {{{LAST_NAME}}}.
unsubscribedbooleanoptionalCreate the contact already unsubscribed (for example when importing a suppression list). Defaults to false.
import { SpacePost } from "@spacepost/node";
const spacepost = new SpacePost(process.env.SPACEPOST_API_KEY);
const { data: audience, error } = await spacepost.audiences.create({ name: "Newsletter" });if (error) throw new Error(error.message);
// Createawait spacepost.contacts.create({ audienceId: audience.id, email: "ada@example.com", firstName: "Ada", lastName: "Lovelace",});
// Read by email (or by id)const { data: contact } = await spacepost.contacts.get({ audienceId: audience.id, email: "ada@example.com" });console.log(contact?.unsubscribed);
// Updateawait spacepost.contacts.update({ audienceId: audience.id, email: "ada@example.com", firstName: "Augusta" });
// List, newest firstconst { data: page } = await spacepost.contacts.list({ audienceId: audience.id, limit: 50 });console.log(page?.data.length, page?.has_more);
// Unsubscribe manually, then deleteawait spacepost.contacts.update({ audienceId: audience.id, email: "ada@example.com", unsubscribed: true });await spacepost.contacts.remove({ audienceId: audience.id, email: "ada@example.com" });AUD=0192e1c4-0000-7000-8000-0000000000a1
# createcurl -X POST https://api.spacepost.dev/audiences/$AUD/contacts \ -H "Authorization: Bearer $SPACEPOST_API_KEY" -H "Content-Type: application/json" \ -d '{ "email": "ada@example.com", "first_name": "Ada", "last_name": "Lovelace" }'# => { "object": "contact", "id": "..." }
# get by email (URL-encode the @) or by idcurl "https://api.spacepost.dev/audiences/$AUD/contacts/ada%40example.com" -H "Authorization: Bearer $SPACEPOST_API_KEY"
# updatecurl -X PATCH "https://api.spacepost.dev/audiences/$AUD/contacts/ada%40example.com" \ -H "Authorization: Bearer $SPACEPOST_API_KEY" -H "Content-Type: application/json" \ -d '{ "unsubscribed": true }'
# deletecurl -X DELETE "https://api.spacepost.dev/audiences/$AUD/contacts/ada%40example.com" -H "Authorization: Bearer $SPACEPOST_API_KEY"base = f"https://api.spacepost.dev/audiences/{audience_id}/contacts"h = {"Authorization": f"Bearer {os.environ['SPACEPOST_API_KEY']}"}
requests.post(base, headers=h, json={"email": "ada@example.com", "first_name": "Ada"})requests.patch(f"{base}/ada@example.com", headers=h, json={"unsubscribed": True})A contact looks like this:
{ "object": "contact", "id": "0192e1c4-0000-7000-8000-0000000000c1", "email": "ada@example.com", "first_name": "Ada", "last_name": "Lovelace", "created_at": "2026-10-23T14:00:00.000Z", "unsubscribed": false}DELETE returns { "object": "contact", "contact": "ada@example.com", "deleted": true }. Where an idOrEmail is expected you can pass either the contact id or the email address.
Unsubscribes
Section titled “Unsubscribes”A contact with unsubscribed: true is skipped by every broadcast. Contacts unsubscribe themselves through the link in your emails (Unsubscribe), and you can set the flag through the API when you learn about an opt-out elsewhere.
Importing
Section titled “Importing”Import with a loop, respecting the rate limit (10 requests per second on Free and Pro):
for (const row of rows) { const { error } = await spacepost.contacts.create({ audienceId, email: row.email, firstName: row.first }); if (error?.name === "rate_limit_exceeded") await new Promise((r) => setTimeout(r, 1000));}Only import people who agreed to hear from you. See Compliance.