Create contact
const url = 'https://api.postlever.com/v1/contacts';const options = { method: 'POST', headers: {Authorization: '<Authorization>', 'Content-Type': 'application/json'}, body: '{"email":"user@example.com","brandIds":["497f6eca-6276-4993-bfeb-53cbbbba6f08"],"attributes":{"first_name":"Alice"}}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request POST \ --url https://api.postlever.com/v1/contacts \ --header 'Authorization: <Authorization>' \ --header 'Content-Type: application/json' \ --data '{ "email": "user@example.com", "brandIds": [ "497f6eca-6276-4993-bfeb-53cbbbba6f08" ], "attributes": { "first_name": "Alice" } }'Adds a contact to your contacts. If a contact with the same email already exists
the request is rejected with 409 contact_exists. Please use the attributes present in the contact schema defined in the portal.
This can also be retrieve from GET /v1/contact-schema.
Handling an email you have sent before
Send duplicateContactStrategy and an existing contact is merged into rather than rejected.
duplicateContactStrategy |
Attributes | Brands |
|---|---|---|
| omitted | Rejected with 409 contact_exists. |
Nothing. |
overwrite |
Every attribute you send is set, filling blanks or replacing current values. Attributes you don’t send keep their value. | brandIds are added to its brands, or replace them if overwriteBrands is true. |
keep |
Only blank attributes are filled in; current values stay. | brandIds are added to its brands. |
skip |
The contact is left as it is, and the request succeeds. | Untouched. |
An attribute sent as an empty string counts as not sent, so it never clears a value; use
PATCH /v1/contacts/{emailMd5} with null to clear one. A contact’s hard bounce, the date
it was first added and how it was first imported are always kept, whatever you send.
Disabled contacts
A disabled contact is left disabled and the request fails with 409 contact_exists.
To bring it back, send restoreDisabled: true with a duplicateContactStrategy. The
contact is enabled again, and:
duplicateContactStrategy |
Attributes | Brands |
|---|---|---|
overwrite |
Every attribute you send is set, filling blanks or replacing current values. Attributes you don’t send keep their value. | brandIds are added to its brands, or replace them if overwriteBrands is true. |
keep |
Only blank attributes are filled in; current values stay. | brandIds are added to its brands. |
skip |
Only the attributes you send; any others are cleared. | Replaced by brandIds if sent, otherwise kept. |
Authorizations
Section titled “Authorizations”Request Bodyrequired
Section titled “Request Bodyrequired”The contact to create. Schema attribute values are provided in the attributes
object; attribute keys must exist in your account’s contact schema (see
GET /v1/contact-schema). The brand
attribute key is
rejected; a contact’s brands are set via brandIds. Derived name:subKey
keys (e.g. email:md5) are computed by PostLever and rejected. An
empty-string value is treated as not provided: the attribute is left off the
created contact rather than rejected. PATCH differs and rejects it.
object
The contact’s email address in plaintext. The contact identity is derived from it and cannot be changed later.
Optional list of brands to add to the contact. Every brand must be an existing brand in your account. Retreive brand ids from GET /v1/brands
When true, a brand-subscribe event is recorded for each brand the
contact is assigned on creation, triggering any downstream subscribe
automations. Defaults to true; set to false to add the brands
without firing automations.
Custom schema attribute values, keyed by attribute name. An attribute this
object does not carry is left unanswered rather than given a value: a
boolean attribute you omit is absent from the contact, which is not the
same as sending false. Each text value holds at most 255 characters.
What to do if a contact with this email already exists. If omitted, the
request fails with 409 contact_exists.
overwrite: set every attribute you send; the ones you don’t send keep their value.keep: only fill in attributes the contact doesn’t have yet.skip: leave the contact unchanged.
By default, brandIds are added to the contact’s existing brands. Set to
true to replace them instead. Only applies with overwrite.
Set to true to enable a disabled contact again instead of failing with
409 contact_exists. Requires duplicateContactStrategy, which decides how its
attributes are merged. See “Disabled contacts” in the endpoint description.
Example
{ "email": "user@example.com", "brandIds": [ "497f6eca-6276-4993-bfeb-53cbbbba6f08" ], "attributes": { "first_name": "Alice" }}Responses
Section titled “Responses”The contact. Returned when it was created, and, with duplicateContactStrategy set,
also when an existing contact was merged into or left unchanged by skip.
A contact in your account.
object
The contact’s email address.
The contact identity, md5(lower(email)), used in single-contact request paths.
Schema attribute values keyed by attribute name. An attribute the contact
has no value for is absent from this object, so a missing boolean key means
no answer was ever recorded for it and is distinct from false. Attributes
of type email additionally expand into one read-only name:subKey entry
per derived sub-attribute (e.g. name:md5, name:domain). The identity
email and the brands are surfaced via the dedicated email and brandIds
fields instead of appearing here.
object
Ids of the brands the contact is assigned to. May include ids of brands
that have since been deleted from the account; resolve names via
GET /v1/contacts/{emailMd5}/brands or GET /v1/brands.
False when the contact has been disabled via POST /v1/contacts/{emailMd5}/disable.
Timestamp at which the contact was first added.
Example
{ "email": "user@example.com", "emailMd5": "b58996c504c5638798eb6b511e6f49af", "attributes": { "first_name": "Alice" }, "brandIds": [ "497f6eca-6276-4993-bfeb-53cbbbba6f08" ], "enabled": true, "createdAt": "2026-06-12T00:00:00Z"}Invalid request payload. Details are provided in the error response.
Error envelope returned by every v1 endpoint for failures, following RFC 9457
Problem Details with code and errors extensions.
object
URI identifying the problem type; dereferences to its documentation.
HTTP status code applicable to this problem.
Short, human-readable summary of the problem type.
Detailed explanation specific to this occurrence.
Error code identifying the specific error condition.
URI reference identifying this specific occurrence (the request URI).
Provides additional information when the error relates to specific request fields.
object
Detailed explanation for this specific field error.
JSON Pointer (RFC 6901) to the request body value that caused the error (e.g. /email, /attributes/firstName).
The URI query parameter that caused the error (e.g. pageSize, name[invalid]).
Example
{ "type": "https://docs.postlever.com/error-codes#invalid_request_error", "status": 400, "title": "Invalid request", "detail": "\"nickname\" is not an attribute in the contact schema", "code": "invalid_request_error", "instance": "/v1/contacts", "errors": [ { "detail": "\"nickname\" is not an attribute in the contact schema", "pointer": "/attributes/nickname" } ]}Missing or invalid API key.
Error envelope returned by every v1 endpoint for failures, following RFC 9457
Problem Details with code and errors extensions.
object
URI identifying the problem type; dereferences to its documentation.
HTTP status code applicable to this problem.
Short, human-readable summary of the problem type.
Detailed explanation specific to this occurrence.
Error code identifying the specific error condition.
URI reference identifying this specific occurrence (the request URI).
Provides additional information when the error relates to specific request fields.
object
Detailed explanation for this specific field error.
JSON Pointer (RFC 6901) to the request body value that caused the error (e.g. /email, /attributes/firstName).
The URI query parameter that caused the error (e.g. pageSize, name[invalid]).
Example
{ "type": "https://docs.postlever.com/error-codes#authentication_error", "status": 401, "title": "Authentication failed", "detail": "Missing or invalid API key", "code": "authentication_error", "instance": "/v1/contacts"}Permission denied.
Error envelope returned by every v1 endpoint for failures, following RFC 9457
Problem Details with code and errors extensions.
object
URI identifying the problem type; dereferences to its documentation.
HTTP status code applicable to this problem.
Short, human-readable summary of the problem type.
Detailed explanation specific to this occurrence.
Error code identifying the specific error condition.
URI reference identifying this specific occurrence (the request URI).
Provides additional information when the error relates to specific request fields.
object
Detailed explanation for this specific field error.
JSON Pointer (RFC 6901) to the request body value that caused the error (e.g. /email, /attributes/firstName).
The URI query parameter that caused the error (e.g. pageSize, name[invalid]).
Example
{ "type": "https://docs.postlever.com/error-codes#permission_error", "status": 403, "title": "Permission denied", "detail": "api key required", "code": "permission_error", "instance": "/v1/contacts"}Returned with code: contact_exists when a contact with this email already
exists and duplicateContactStrategy was not sent, when the contact is
disabled and restoreDisabled was not sent, or when the email belongs to a
seed. Also returned with code: operation_in_progress when a bulk contact operation or schema change is in flight for the account.
In that case retry after the number of seconds given in the Retry-After
header.
Error envelope returned by every v1 endpoint for failures, following RFC 9457
Problem Details with code and errors extensions.
object
URI identifying the problem type; dereferences to its documentation.
HTTP status code applicable to this problem.
Short, human-readable summary of the problem type.
Detailed explanation specific to this occurrence.
Error code identifying the specific error condition.
URI reference identifying this specific occurrence (the request URI).
Provides additional information when the error relates to specific request fields.
object
Detailed explanation for this specific field error.
JSON Pointer (RFC 6901) to the request body value that caused the error (e.g. /email, /attributes/firstName).
The URI query parameter that caused the error (e.g. pageSize, name[invalid]).
Examples
A contact with this email already exists
{ "type": "https://docs.postlever.com/error-codes#contact_exists", "status": 409, "title": "Conflict", "detail": "A contact with this email already exists", "code": "contact_exists", "instance": "/v1/contacts"}A bulk operation blocks the mutation
{ "type": "https://docs.postlever.com/error-codes#operation_in_progress", "status": 409, "title": "Conflict", "detail": "A bulk contact operation or schema change is in progress for this account, retry later", "code": "operation_in_progress", "instance": "/v1/contacts"}Headers
Section titled “Headers”Seconds to wait before retrying. Present only when code is operation_in_progress.