Skip to main content
Create, read, update, and delete contacts in your Benchmark Email account using the API.

Goal

By the end of this guide you will be able to perform full CRUD operations on contacts: retrieve your contact structure (to get field IDs), create new contacts, retrieve them individually, update their fields or status, and delete them.

Prerequisites

  • An API key with contacts:write scope (for full CRUD) or contacts:read scope (for read-only access)
  • Your API base URL (found on the Account Settings > API Keys page)

About contact status

Every contact has a status field that tracks whether they’re reachable. The shape depends on the contact’s state. Active contacts have only a primary status:
"status": {
  "primary": "Active"
}
Inactive contacts have both a primary status and a secondary status. The secondary explains why the contact is inactive:
"status": {
  "primary": "Inactive",
  "secondary": "Unsubscribe"
}
A few rules to know:
  • Status values are case-sensitive. Active, Inactive, and Unsubscribe must use exact capitalization. Lowercase values such as "active" are rejected with a validation error.
  • Customers can only set Unsubscribe as the secondary value via the API. This is the only customer-initiated reason for inactivating a contact. Other secondary values appear on contacts the system has inactivated automatically based on email events, and cannot be set through API calls. The values you may see on contacts the system has inactivated include:
    • Bounce — the contact’s email address hard-bounced
    • Complaint-FBL — the contact’s mailbox provider reported a spam complaint via the Feedback Loop (FBL) mechanism
  • When transitioning a contact to Inactive, both primary and secondary are required. Sending only primary: "Inactive" returns a validation error.
  • Once a contact is Inactive, it cannot be reactivated via the API. This is a deliberate compliance protection — customers who unsubscribe or hard-bounce should not be re-emailed. To re-add a contact, they must opt in again through your sign-up flow.

Steps

1. Fetch your contact structure

Before creating contacts, retrieve your contact structure to get the field IDs you will need. Every contact belongs to a contact structure, and field values are set by referencing each field’s _id.
curl "<your-api-base-url>/api/contact-structure" \
  -H "X-API-Key: bme_us_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v"
Response (200 OK):
[
  {
    "_id": "64a1b2c3d4e5f6a7b8c9d0e1",
    "label": "Default Contacts",
    "keyName": "Email",
    "keyType": "email",
    "fields": [
      { "_id": "64a1b2c3d4e5f6a7b8c9d100", "label": "First Name", "dataType": "text", "predefinedField": "firstName" },
      { "_id": "64a1b2c3d4e5f6a7b8c9d101", "label": "Last Name", "dataType": "text", "predefinedField": "lastName" }
    ],
    "tags": [
      { "_id": "64a1b2c3d4e5f6a7b8c9d300", "label": "VIP" }
    ]
  }
]
Save the contact structure _id and the field _id values. You will use these when creating and updating contacts. See Manage Custom Fields for more details on contact structures.

2. Create a contact

Send a POST request with the contact’s email address (key), the contact structure it belongs to, and optionally any custom field values and list assignments.
curl -X POST "<your-api-base-url>/api/contact" \
  -H "X-API-Key: bme_us_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v" \
  -H "Content-Type: application/json" \
  -d '{
    "key": "jane.smith@example.com",
    "contactStructureId": "64a1b2c3d4e5f6a7b8c9d0e1",
    "fields": [
      { "_id": "64a1b2c3d4e5f6a7b8c9d100", "value": "Jane" },
      { "_id": "64a1b2c3d4e5f6a7b8c9d101", "value": "Smith" }
    ],
    "lists": [
      { "_id": "64a1b2c3d4e5f6a7b8c9d200" }
    ]
  }'
Response (200 OK):
{
  "_id": "66f1a6e4698f1bca60424901",
  "key": "jane.smith@example.com",
  "contactStructureId": "64a1b2c3d4e5f6a7b8c9d0e1",
  "fields": [
    { "_id": "64a1b2c3d4e5f6a7b8c9d100", "value": "Jane" },
    { "_id": "64a1b2c3d4e5f6a7b8c9d101", "value": "Smith" }
  ],
  "lists": [
    { "_id": "64a1b2c3d4e5f6a7b8c9d200" }
  ],
  "tags": [],
  "status": {
    "primary": "Active"
  },
  "createdAt": "2026-03-28T14:30:00.000Z",
  "updatedAt": "2026-03-28T14:30:00.000Z",
  "__v": 0
}
Responses also include accountId, createdBy, and modifiedBy fields, which are auto-populated by the system and read-only. They’re omitted from the samples here for clarity. Key points:
  • key is the contact’s email address (required)
  • contactStructureId is required — it determines which custom fields are available
  • fields uses the field _id from your contact structure (see Step 1)
  • lists optionally assigns the contact to one or more lists at creation time
  • New contacts start with __v: 0. You’ll need this version value when updating the contact via PUT.

3. Get a contact by ID

Retrieve a single contact using its _id.
curl "<your-api-base-url>/api/contact/66f1a6e4698f1bca60424901" \
  -H "X-API-Key: bme_us_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v"
Response (200 OK):
{
  "_id": "66f1a6e4698f1bca60424901",
  "key": "jane.smith@example.com",
  "contactStructureId": "64a1b2c3d4e5f6a7b8c9d0e1",
  "fields": [
    { "_id": "64a1b2c3d4e5f6a7b8c9d100", "value": "Jane" },
    { "_id": "64a1b2c3d4e5f6a7b8c9d101", "value": "Smith" }
  ],
  "lists": [
    { "_id": "64a1b2c3d4e5f6a7b8c9d200" }
  ],
  "tags": [],
  "status": {
    "primary": "Active"
  },
  "createdAt": "2026-03-28T14:30:00.000Z",
  "updatedAt": "2026-03-28T14:30:00.000Z",
  "__v": 3
}
The __v field is the contact’s current version. You’ll need this exact value when updating the contact via PUT (see Step 5).

4. Update a contact’s status

Use PATCH to update a contact’s status. The most common case is unsubscribing a contact:
curl -X PATCH "<your-api-base-url>/api/contact/66f1a6e4698f1bca60424901" \
  -H "X-API-Key: bme_us_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v" \
  -H "Content-Type: application/json" \
  -d '{
    "status": {
      "primary": "Inactive",
      "secondary": "Unsubscribe"
    }
  }'
Response (200 OK):
{
  "_id": "66f1a6e4698f1bca60424901",
  "key": "jane.smith@example.com",
  "contactStructureId": "64a1b2c3d4e5f6a7b8c9d0e1",
  "fields": [
    { "_id": "64a1b2c3d4e5f6a7b8c9d100", "value": "Jane" },
    { "_id": "64a1b2c3d4e5f6a7b8c9d101", "value": "Smith" }
  ],
  "lists": [
    { "_id": "64a1b2c3d4e5f6a7b8c9d200" }
  ],
  "tags": [],
  "status": {
    "primary": "Inactive",
    "secondary": "Unsubscribe"
  },
  "createdAt": "2026-03-28T14:30:00.000Z",
  "updatedAt": "2026-03-28T15:10:00.000Z",
  "__v": 4
}
Notes:
  • PATCH only accepts the status field. To update other fields (email, custom fields, lists, tags), use PUT (see Step 5).
  • Both primary and secondary are required when transitioning a contact to Inactive.
  • Inactive contacts cannot be reactivated via the API. See About contact status above.

5. Replace a contact (full update)

Use PUT to update any field on a contact: email, custom fields, lists, tags, or status. Unlike PATCH, PUT accepts the full contact body. Important: PUT replaces the entire contact. Any field you omit from the request body will be removed from the contact’s stored data. To avoid accidentally losing data, follow these three steps:
  1. (If needed) Find the contact’s _id via Search Contacts.
  2. Send a GET request to retrieve the contact’s full data, including its current __v version.
  3. Send a PUT request using the GET response as your starting body. Change only the fields you want to update, and include __v exactly as you received it from the GET.
curl -X PUT "<your-api-base-url>/api/contact/66f1a6e4698f1bca60424901" \
  -H "X-API-Key: bme_us_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v" \
  -H "Content-Type: application/json" \
  -d '{
    "key": "jane.smith@example.com",
    "contactStructureId": "64a1b2c3d4e5f6a7b8c9d0e1",
    "fields": [
      { "_id": "64a1b2c3d4e5f6a7b8c9d100", "value": "Jane" },
      { "_id": "64a1b2c3d4e5f6a7b8c9d101", "value": "Smith-Jones" }
    ],
    "lists": [
      { "_id": "64a1b2c3d4e5f6a7b8c9d200" },
      { "_id": "64a1b2c3d4e5f6a7b8c9d201" }
    ],
    "tags": [],
    "status": {
      "primary": "Active"
    },
    "__v": 3
  }'
Response (200 OK):
{
  "_id": "66f1a6e4698f1bca60424901",
  "key": "jane.smith@example.com",
  "contactStructureId": "64a1b2c3d4e5f6a7b8c9d0e1",
  "fields": [
    { "_id": "64a1b2c3d4e5f6a7b8c9d100", "value": "Jane" },
    { "_id": "64a1b2c3d4e5f6a7b8c9d101", "value": "Smith-Jones" }
  ],
  "lists": [
    { "_id": "64a1b2c3d4e5f6a7b8c9d200" },
    { "_id": "64a1b2c3d4e5f6a7b8c9d201" }
  ],
  "tags": [],
  "status": {
    "primary": "Active"
  },
  "createdAt": "2026-03-28T14:30:00.000Z",
  "updatedAt": "2026-03-28T16:00:00.000Z",
  "__v": 4
}
Notes on __v:
  • __v is required on every PUT request. It’s the contact’s optimistic-concurrency version, which prevents one update from silently overwriting another.
  • Always use the __v from a fresh GET — not a value cached from earlier. Successful PATCH or PUT calls increment __v, so a stale value will be rejected.
  • If __v does not match the contact’s current version, the API returns a 400 ConcurrencyError. Re-run the GET, rebuild your PUT body, and retry.
Notes on status in PUT:
  • Status follows the same rules as PATCH: capitalized values, both primary and secondary required for Inactive.
  • For an Active contact, send "status": { "primary": "Active" } (no secondary).
  • PUT cannot change an Inactive contact back to Active — see About contact status.

6. Delete a contact

curl -X DELETE "<your-api-base-url>/api/contact/66f1a6e4698f1bca60424901" \
  -H "X-API-Key: bme_us_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v"
Response (200 OK): Returns the deleted contact object.

7. List all contacts

Retrieve all contacts for your account. This endpoint returns all matching contacts in a single response (no pagination). For large contact databases, use Search Contacts instead, which supports pagination and filtering.
curl "<your-api-base-url>/api/contact" \
  -H "X-API-Key: bme_us_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v"
Response (200 OK):
[
  {
    "_id": "66f1a6e4698f1bca60424901",
    "key": "jane.smith@example.com",
    "contactStructureId": "64a1b2c3d4e5f6a7b8c9d0e1",
    "fields": [
      { "_id": "64a1b2c3d4e5f6a7b8c9d100", "value": "Jane" },
      { "_id": "64a1b2c3d4e5f6a7b8c9d101", "value": "Smith" }
    ],
    "lists": [
      { "_id": "64a1b2c3d4e5f6a7b8c9d200" }
    ],
    "tags": [],
    "status": {
      "primary": "Active"
    },
    "createdAt": "2026-03-28T14:30:00.000Z",
    "updatedAt": "2026-03-28T14:30:00.000Z",
    "__v": 0
  }
]

Common errors

StatusErrorCauseFix
401UnauthorizedErrorInvalid, expired, or inactive API keyVerify your key is correct and active in Settings > API Keys
403ForbiddenErrorKey lacks the required scopeUse a key with contacts:write for create/update/delete operations
400RequiredFieldErrorA required field is missing from the request bodyCheck the error’s field and message for the specific field name
400ModelValidationErrorA field value fails validation (wrong type, invalid enum value, missing case-paired field, blocked transition)Read the message and debug for specifics. Common cases: lowercase status values, transitioning Inactive → Active, missing secondary when setting primary: "Inactive"
400ConcurrencyErrorThe __v value in your PUT body does not match the contact’s current versionRun a fresh GET, rebuild your PUT body using the new __v, and retry
400DuplicateFieldErrorA contact with the same email already exists in this structureUse Search Contacts to look up the existing contact, then update it instead of creating a new one
404RecordNotFoundContact ID does not existVerify the contact ID; it may have been deleted
429TooManyRequestsErrorRate limit or monthly quota exceededWait for the Retry-After period and retry. See Rate Limits

Next steps