Contacts
API reference for managing marketing contacts (subscribers) with Envloped
Contacts
Contacts are the subscribers in your marketing audience. Every contact belongs to
your account, carries a consent record, and has a lifecycle status. Contacts can
be grouped into Lists for campaign targeting.
All endpoints require an Authorization: Bearer YOUR_API_KEY header.
The contact object
{
"id": "ct_abc123",
"email": "jane@example.com",
"email_normalized": "jane@example.com",
"first_name": "Jane",
"last_name": "Doe",
"attributes": { "plan": "pro" },
"status": "subscribed",
"consent_source": "api",
"consent_at": "2026-01-15T10:30:00Z",
"unsubscribed_at": null,
"created_at": "2026-01-15T10:30:00Z",
"updated_at": "2026-01-15T10:30:00Z"
}| Field | Type | Description |
|---|---|---|
id | string | Unique contact identifier. |
email | string | Email address as provided. |
email_normalized | string | Normalized address used for de-duplication and suppression cross-checks. |
first_name | string | null | First name. |
last_name | string | null | Last name. |
attributes | object | Arbitrary key/value data for merge tags and segmentation. |
status | string | One of subscribed, unsubscribed, bounced, complained, suppressed, pending. |
consent_source | string | null | How consent was captured: api, csv_import, form, manual. |
consent_at | string | null | ISO 8601 timestamp consent was recorded. |
unsubscribed_at | string | null | ISO 8601 timestamp of global opt-out, if any. |
created_at | string | ISO 8601 creation timestamp. |
updated_at | string | ISO 8601 last-update timestamp. |
List contacts
GET /v1/contactsReturns a paginated page of contacts.
Query parameters
| Parameter | Type | Description |
|---|---|---|
page | number | 1-based page number (default 1). |
limit | number | Page size, 1–100 (default 50). |
status | string | Filter by lifecycle status. |
list_id | string | Restrict to members of a given list. |
search | string | Case-insensitive email substring match. |
Response
{
"contacts": [ /* contact objects */ ],
"total": 1280,
"page": 1,
"totalPages": 26
}curl "https://api.envloped.com/v1/contacts?status=subscribed&limit=50" \
-H "Authorization: Bearer en_abc123..."Create a contact
POST /v1/contactsCreates a single contact and, optionally, adds it to lists. This is a strict
create — a contact with the same normalized email already existing returns 409.
Request body
| Parameter | Type | Required | Description |
|---|---|---|---|
email | string | Yes | Email address. |
first_name | string | No | First name. |
last_name | string | No | Last name. |
attributes | object | No | Arbitrary attributes for merge tags / segmentation. |
consent_source | string | No | api, csv_import, form, or manual (default api). |
list_ids | string[] | No | Lists to add the new contact to. Unknown ids are reported in invalid_lists. |
Consent is stamped at creation time (consent_at = now()), and the caller IP is
recorded on the consent record.
Response — 201 Created
{
"contact": { /* contact object */ },
"added_to_lists": ["ls_newsletter"],
"invalid_lists": []
}curl -X POST https://api.envloped.com/v1/contacts \
-H "Authorization: Bearer en_abc123..." \
-H "Content-Type: application/json" \
-d '{
"email": "jane@example.com",
"first_name": "Jane",
"attributes": { "plan": "pro" },
"list_ids": ["ls_newsletter"]
}'Errors
| Status | error | Meaning |
|---|---|---|
400 | invalid_email | The email failed validation. |
409 | duplicate_contact | A contact with this normalized email already exists. |
Retrieve a contact
GET /v1/contacts/:idReturns the contact plus its live list memberships.
{
"contact": { /* contact object */ },
"lists": [
{ "id": "ls_newsletter", "name": "Newsletter", "unsubscribed_at": null }
]
}Returns 404 with { "error": "Contact not found" } if the id is unknown.
Update a contact
PATCH /v1/contacts/:idUpdates mutable fields. Only supplied fields change; attributes is replaced
wholesale when provided. Setting status to unsubscribed stamps
unsubscribed_at; moving to subscribed/pending clears it.
Request body
| Parameter | Type | Description |
|---|---|---|
first_name | string | null | New first name (null clears it). |
last_name | string | null | New last name (null clears it). |
attributes | object | Replacement attributes object. |
status | string | New lifecycle status. |
curl -X PATCH https://api.envloped.com/v1/contacts/ct_abc123 \
-H "Authorization: Bearer en_abc123..." \
-H "Content-Type: application/json" \
-d '{ "status": "unsubscribed" }'Response: { "contact": { /* updated contact */ } }.
Delete a contact (GDPR erasure)
DELETE /v1/contacts/:idHard delete. The contact row is removed permanently and its list memberships are cascaded away. Campaign history is preserved with the contact reference nulled, so aggregate reports stay intact while the PII is gone. This is the endpoint to satisfy a right-to-erasure request.
{ "deleted": true, "id": "ct_abc123" }Export contacts
GET /v1/contacts/exportStreams the full contact list as a CSV file (Content-Type: text/csv) with the
columns email, first_name, last_name, status, consent_source, consent_at, unsubscribed_at, created_at. The export is keyset-paginated server-side so it
stays memory-flat for very large audiences.
curl "https://api.envloped.com/v1/contacts/export" \
-H "Authorization: Bearer en_abc123..." \
-o contacts.csvSDKs & CLI
The same operations are available in the official SDKs and CLI:
// JavaScript / TypeScript
await client.contacts.create({ email: 'jane@example.com', list_ids: ['ls_newsletter'] });
const { contacts } = await client.contacts.list({ status: 'subscribed' });// Go
client.Contacts.Create(&envloped.CreateContactRequest{Email: "jane@example.com"})# CLI
envloped contacts create jane@example.com --list ls_newsletter
envloped contacts list --status subscribed