Appearance
Create a contact
Add a person to your audience.
http
POST /v1/contactsRequires a Full access API key — contacts are people's addresses, not sending, so a sending key embedded in an app cannot read or write them.
Body parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
email | string | Yes | The contact's email address. Max 254 characters. Stored lower-cased, and unique within your team. |
first_name | string | null | No | Max 120 characters. |
last_name | string | null | No | Max 120 characters. |
unsubscribed | boolean | No | Defaults to false. true marks the contact globally unsubscribed from all broadcasts and stamps unsubscribed_at. |
properties | object | No | A flat map of contact property key → value. Keys that aren't properties on your team are ignored. |
segments | array | No | Segments to attach the contact to, each an object { "id": "<segment id>" }. Every id must be a segment on your team. |
topics | array | No | Topic subscriptions to set, each { "id": "<topic id>", "subscription": "opt_in" | "opt_out" }. opt_in subscribes, opt_out unsubscribes. |
The created contact's source is api.
Example
bash
curl -X POST 'https://api.sendgrail.com/v1/contacts' \
-H "Authorization: Bearer $SENDGRAIL_API_KEY" \
-H 'Content-Type: application/json' \
-d '{
"email": "ada@example.com",
"first_name": "Ada",
"last_name": "Lovelace",
"properties": {
"company_name": "Analytical Engines Ltd",
"seats": 12
}
}'js
const res = await fetch('https://api.sendgrail.com/v1/contacts', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.SENDGRAIL_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: 'ada@example.com',
first_name: 'Ada',
last_name: 'Lovelace',
properties: {
company_name: 'Analytical Engines Ltd',
seats: 12,
},
}),
});
const contact = await res.json();Response
201 Created. The body carries just the new contact's id — fetch the full contact object with retrieve if you need it:
json
{
"object": "contact",
"id": "3f1c9a0e-6c5e-4a2b-9f2d-2b1a7c8e4d31"
}A contact.created webhook event fires on success.
Attaching segments and topics
Pass segments and topics to place the contact into segments and set topic subscriptions in the same call, with no follow-up requests. segments is an array of { "id" }; topics is an array of { "id", "subscription" }, where subscription is opt_in or opt_out.
bash
curl -X POST 'https://api.sendgrail.com/v1/contacts' \
-H "Authorization: Bearer $SENDGRAIL_API_KEY" \
-H 'Content-Type: application/json' \
-d '{
"email": "grace@example.com",
"first_name": "Grace",
"unsubscribed": false,
"segments": [
{ "id": "7a2b6f10-5c94-4f7d-9a3e-8c1b0d6e2f45" }
],
"topics": [
{ "id": "b1e7c3d9-42a6-4c0f-8b5e-9d3f1a7c6e20", "subscription": "opt_in" },
{ "id": "e5c8f2a1-70b4-4d19-9c3a-6f2e8b0d1a74", "subscription": "opt_out" }
]
}'js
const res = await fetch('https://api.sendgrail.com/v1/contacts', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.SENDGRAIL_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: 'grace@example.com',
first_name: 'Grace',
unsubscribed: false,
segments: [{ id: '7a2b6f10-5c94-4f7d-9a3e-8c1b0d6e2f45' }],
topics: [
{ id: 'b1e7c3d9-42a6-4c0f-8b5e-9d3f1a7c6e20', subscription: 'opt_in' },
{ id: 'e5c8f2a1-70b4-4d19-9c3a-6f2e8b0d1a74', subscription: 'opt_out' },
],
}),
});
const contact = await res.json();A segment or topic id that isn't one of your team's is a 404. See Contact segments and Contact topics for managing these after creation.
The email address is the identity
One address, one contact, per team. Creating a contact whose address already exists is a 422, not a silent merge:
json
{
"message": "The email has already been taken.",
"code": "validation_error",
"errors": {
"email": ["The email has already been taken."]
},
"request_id": "8f2c1e64-1b17-4f8c-9f0e-3b6c1e0f5c31"
}Addresses are normalised to lower case on the way in, so Ada@Example.com and ada@example.com are the same contact — which is also why you can address a contact by its address in the path (see Retrieve a contact). If you want create-or-update behaviour, update the contact by its email address and fall back to creating it on a 404.
Properties are validated loosely on purpose
A value for a number-typed property must be numeric, or the request is a 422:
json
{
"message": "The seats property must be a number.",
"code": "validation_error",
"errors": {
"properties.seats": ["The seats property must be a number."]
},
"request_id": "8f2c1e64-1b17-4f8c-9f0e-3b6c1e0f5c31"
}A key that isn't a property on your team is ignored, not rejected. A property someone deletes in the dashboard between two of your calls would otherwise turn a working integration into a stream of 422s — and there is nothing your code could usefully do about it.
Send null or "" for a key to clear the contact's value for it, which makes the property fall back to its fallback again.
See Errors for the error envelope.