Skip to content

Contact API - Fields Reference

This article describes all fields available through the Contact API. Use this as a reference when reading, creating, or updating contacts.


The Contact API lets you read, create, update, and delete the contacts in your Envoke account. A contact is one person: their email address and profile fields, their consent status, the values of your account’s custom fields, and the tags, subscriptions, and nurture campaigns (autoresponders) they belong to.

The Contact API answers on /v1 only. Related resources have their own APIs: custom fields are defined through the Custom Field API, and tags and subscriptions through the Tag API and Subscription API. Those APIs also offer bulk add/remove endpoints for contacts; on the Contact API, tags, subscriptions, and mandatory consent are all written through the single interests field.

All endpoints require HTTP Basic Auth using your API ID and API Key.


Field Type Read-only Required on Create Description
id string Yes — Envoke contact ID, assigned on creation.
external_id string Yes — Externally visible identifier used in public-facing links.
date_created datetime Yes — When the contact was created, in UTC.
remote_id string No Only with key_field=remote_id Your own identifier for the contact, e.g. a CRM or membership ID.
first_name string No No First name.
last_name string No No Last name.
title string No No Job title or honorific.
email string No Yes Email address.
company string No No Company or organisation.
phone string No No Phone number (free text).
address_1 string No No Street address, line 1.
address_2 string No No Street address, line 2.
city string No No City.
country string No No Country, returned as a 2-letter ISO code.
province string No No Province or state (free text).
postal_code string No No Postal or ZIP code.
website string No No Website URL (free text).
language string No No Preferred language, en or fr.
feedback string No No Free-text notes. HTML is stripped.
consent_status string No No Current consent type. Defaults to Not Provided on create.
consent_description string No No How and when consent was obtained.
consent_effective_date datetime No No When the current consent took effect, in UTC.
consent_expiry string Yes — Expiry date of the current consent (YYYY-MM-DD), or "".
consent_force_downgrade boolean Write-only No Forces a consent change that would otherwise be ignored as a downgrade. Never returned.
custom_fields object No No Custom field values keyed by custom field name.
interests array No No Names of the contact’s tags, subscriptions, and mandatory consent items.
autoresponders array No No Names of the nurture campaigns the contact is on.

Every field is present in a read response unless fields is used to select some. An unset value is returned as "" (never null); this also applies to a stored value of "0". custom_fields is returned as {} and interests and autoresponders as [] when empty. Text fields have the maximum lengths shown below; a longer value fails validation with the message given (see Error Responses). On some older accounts, columns are shorter than the limits given here; there, a value that passes validation is cut to the column’s length without an error.

  • Type: String
  • Read-only: Yes — assigned automatically on creation.
  • Returned value (read): The contact ID as a string.
  • Example: "1001"

Ignored if sent on create. On update, it identifies the contact (see Update). The exceptions to the string type are the PATCH /v1/contacts/{id} response and the per-contact results of a bulk update, whose result_data.id is an integer (see Update).


  • Type: String (32 characters)
  • Read-only: Yes — assigned automatically on creation.
  • Example: "9f2c4e7a1b3d5f6a8c0e2b4d6f8a1c3e"

An externally visible identifier used in public-facing contexts such as preference-page links. Not the same as id. Silently ignored if sent on create or update.


  • Type: Datetime
  • Read-only: Yes — set automatically on creation.
  • Returned value (read): W3C format in UTC.
  • Example: "2026-10-05T14:03:11+00:00"

Silently ignored if sent on create or update. Supports the range filter operators (see Read).


  • Type: String, max 60 characters
  • Read-only: No
  • Required on create: Only when key_field=remote_id is set on the request.
  • Returned value (read): The stored value, or "" when unset.
  • Example: "HCS-0002"

Your own identifier for the contact. It can be used instead of email to find a contact on create (key_field=remote_id) and on update (key_field=remote_id). Too long: Remote ID must not exceed 60 characters.


  • Type: String, max 50 characters
  • Read-only: No
  • Required on create: No
  • Example: "Maya"

Too long: First name must not exceed 50 characters.


  • Type: String, max 50 characters
  • Read-only: No
  • Required on create: No
  • Example: "Okafor"

Too long: Last name must not exceed 50 characters.


  • Type: String, max 50 characters
  • Read-only: No
  • Required on create: No
  • Example: "Section Lead"

Too long: Title must not exceed 50 characters.


  • Type: String, max 60 characters
  • Read-only: No
  • Required on create: Yes, including when key_field=remote_id is used.
  • Accepted values (write): A valid email address, sent as a JSON string.
  • Example: "alto.lead@example.com"

Errors on create: Email is required (missing or empty), Email must be a string (any non-string JSON value), Email invalid (bad format), Email must not exceed 60 characters. On update, email can be changed but not cleared: Email must not be empty.

Each account has one duplicate-check field (usually email). A create or update that would give two contacts the same value fails with contact with email = {value} already exists. Envoke support can change this setting for accounts that keep several contacts per email address and key on remote_id.


  • Type: String, max 80 characters
  • Read-only: No
  • Required on create: No
  • Example: "Harbourfront Choral Society"

Too long: Company must not exceed 80 characters.


  • Type: String, max 25 characters
  • Read-only: No
  • Required on create: No
  • Example: "416-555-0142"

Free text; no format check. Too long: Phone must not exceed 25 characters.


  • Type: String, max 100 characters
  • Read-only: No
  • Required on create: No
  • Example: "48 Harbour Lane"

Too long: Address must not exceed 100 characters.


  • Type: String, max 100 characters
  • Read-only: No
  • Required on create: No
  • Example: "Unit 5"

Too long: Address 2 must not exceed 100 characters.


  • Type: String, max 50 characters
  • Read-only: No
  • Required on create: No
  • Example: "Toronto"

Too long: City must not exceed 50 characters.


  • Type: String
  • Read-only: No
  • Required on create: No
  • Accepted values (write): A 2-letter ISO code ("CA") or a recognised country name ("Canada", "France", "U.S.A.", "UK").
  • Returned value (read): The 2-letter ISO code, or "".
  • Example: "CA"

A value that is not recognised is not saved and produces a warning (see Warnings): 'Narnia' is not a valid value for country; field will not be updated. The country decides which consent rules apply to the contact (see Consent fields); a contact with no country is treated as being in your account’s default consent country.


  • Type: String, max 50 characters
  • Read-only: No
  • Required on create: No
  • Example: "ON"

Free text; no list check. Too long: Province must not exceed 50 characters.


  • Type: String, max 15 characters
  • Read-only: No
  • Required on create: No
  • Example: "M5V 0Z9"

Too long: Postal / zip must not exceed 15 characters.


  • Type: String, max 255 characters
  • Read-only: No
  • Required on create: No
  • Example: "https://example.org"

Free text; no URL check. Too long: Website must not exceed 255 characters.


  • Type: String
  • Read-only: No
  • Required on create: No
  • Accepted values (write): en or fr, or the names English or French. Case-insensitive.
  • Returned value (read): "en", "fr", or "".
  • Example: "en"

Any other value is not saved and produces a warning (see Warnings): 'de' is not a valid value for language; field will not be updated.


  • Type: String
  • Read-only: No
  • Required on create: No
  • Example: "Happy to help with ushering at the winter concert."

HTML tags are removed before the value is saved. No error is returned when this happens.


  • Type: String
  • Read-only: No
  • Required on create: No — a new contact without one is given Not Provided.
  • Accepted values (write): One of the writable consent types in the Consent fields table. Case-insensitive; a space and an underscore are treated as the same character.
  • Returned value (read): The name of the contact’s current consent type.
  • Example: "Express"

An unknown value fails validation with the list of writable values: Invalid 'consent_status': 'Opted In' (possible values: 'Express', 'Implied - Inquiry', 'Implied - Transaction', 'Implied - No Expiry', 'Not Provided', 'Revoked', 'Envoke Spam Reported'). A valid value does not always become current; see the transition rules under Consent fields.


  • Type: String, max 500 characters
  • Read-only: No
  • Required on create: No
  • Example: "Signed up at the spring concert, 2026-09-12"

A record of how consent was obtained. Text beyond 500 characters is cut off without an error. Not required, even when consent_status changes. Sending only consent_description or consent_effective_date on update re-submits the contact’s current status with the new details, subject to the same transition rules.


  • Type: Datetime
  • Read-only: No
  • Required on create: No — defaults to the time of the request.
  • Accepted values (write): A date and time, e.g. "2026-09-12T18:30:00Z" or "2026-09-12 18:30:00". Without a time zone, the time is read as UTC (see Dates).
  • Returned value (read): W3C format in UTC.
  • Example: "2026-09-12T18:30:00+00:00"

A date in the future fails with consent_effective_date cannot be in the future.; an unreadable value fails with Invalid date format for consent_effective_date. The expiry of implied consent is calculated from this date.


  • Type: String (YYYY-MM-DD)
  • Read-only: Yes — calculated from consent_status and consent_effective_date.
  • Returned value (read): The expiry date, or "" when the consent does not expire or expiry does not apply to the contact.
  • Example: "2027-03-12"

Silently ignored if sent on create or update. See Consent fields for when it is blank.


  • Type: Boolean
  • Read-only: Write-only — never returned.
  • Required on create: No
  • Accepted values (write): true, "true", 1, "1", "yes", "on", or "Set". Anything else is treated as false.
  • Example: true

Sent alongside consent_status to make a downgrade take effect (see Consent fields). Sent on its own, it does nothing.


  • Type: Object
  • Read-only: No
  • Required on create: No
  • Format: { "<custom field name>": "<value>" }
  • Returned value (read): An object of every custom field that has a value for the contact, keyed by the field name exactly as defined in your account. {} when there are none.
  • Example: { "Voice_Part": "Alto", "Season_Ticket_Number": "HCS-ST-0417" }
Aspect Behaviour
Keys on write The custom field name. Matching ignores case and treats a space and an underscore as the same, so "voice part" writes to Voice_Part. Custom field IDs are not accepted as keys.
Unknown key Invalid 'custom_field': 'Voice Range' (for a list of valid custom fields try the endpoint '/customfields')
Not an object 'custom_fields' must be an object
Values Stored as text. Values are not checked against the field’s type.
"" value Deletes the contact’s value for that field. On update this works without write_empty_values (see Update).
Filter and sort custom_fields.{name} (see Read). The name is case-sensitive there.

Fields you do not include are left unchanged; there is no way to replace the whole object in one request.

Note: A value that cannot be stored (for example null) is skipped, and the response still reports success.

Note: A custom field defined for conversion pages (lead scope) is accepted by name but never saved, and is not returned on read.


The consent fields are consent_status, consent_description, consent_effective_date, consent_expiry, and consent_force_downgrade. Together they record the contact’s current consent to receive email. Every change adds an entry to the contact’s consent history; the transition rules below decide whether that entry becomes current.

Name Sending allowed Writable via API Blocks delete Notes
Express Yes Yes No Does not expire.
Implied - Inquiry Yes Yes No Expires 6 months after the effective date. Not applicable in the EU region.
Implied - Transaction Yes Yes No Expires 2 years after the effective date. Not applicable in the EU region.
Implied - No Expiry Yes Yes No Does not expire.
Not Provided No Yes No Default for new contacts.
Revoked No Yes Yes The contact unsubscribed.
Envoke Spam Reported No Yes Yes Also blocks subscriptions and later changes (see below).
Expired No No No Set automatically when implied consent expires.
External Spam Reported No No Yes Set automatically from a spam complaint. Blocks subscriptions and later changes.

Older contacts can also be returned with one of the retired types Implied - Existing Relationship, Legacy Subscribed, Legacy Confirmed, or Legacy Unknown. These cannot be written.

Change Outcome
Upgrade or equal change (e.g. Not Provided → Express, Implied - Inquiry → Implied - Transaction) Becomes current.
Downgrade, called preempted (e.g. Express → any implied type, a non-expiring type → an expiring one, a later expiry → a sooner one, any type → Not Provided) Logged to history but not made current. Send consent_force_downgrade: true to make it current.
From Envoke Spam Reported or External Spam Reported to any other type Blocked: nothing is recorded. consent_force_downgrade does not override this.
Revoked → Not Provided Blocked: nothing is recorded. consent_force_downgrade does not override this.
Revoked → Express or another sending type Becomes current.

Note: A preempted or blocked change is not reported. The response is the normal success message, and the contact’s consent_status is unchanged.

On update, consent is written only when consent_status, consent_description, or consent_effective_date differs from the stored value. Sending the current status again, with no other consent change, does nothing.

Implied - Inquiry expires 6 months, and Implied - Transaction 2 years, after consent_effective_date (or after the time of the request when no date is sent). Other writable types do not expire. If the calculated expiry is already in the past, the contact’s status is set to Expired straight away.

consent_expiry is returned as "" on GET when the consent does not expire, when the contact’s country is not CA (a contact with no country uses your account’s default consent country), or when your account is set to ignore consent expiry. This blanking applies only to GET responses: the full contact returned by a create, an upsert, or PATCH /v1/contacts can show an expiry date that GET then returns as "".

Implied - Inquiry and Implied - Transaction do not apply to contacts in the EU consent region: AT, BE, BG, CY, CZ, DE, DK, EE, ES, FI, FR, GB, GF, GI, GP, GR, HR, HU, IE, IS, IT, LI, LT, LU, LV, MF, MQ, MT, NL, NO, PL, PT, RE, RO, SE, SI, SK, YT. Two cases add a sentence to the end of result_text; the request still succeeds.

Case Sentence appended to result_text
An implied type is written for an EU contact Their consent status was not updated because Implied - Inquiry does not apply to contacts in the EU. The status is left as it was. For a new contact, or when consent_force_downgrade is sent, the wording is set to Not Provided instead of not updated, and the status becomes Not Provided.
An existing contact with an implied type has country changed to an EU country, with no consent fields in the same request Their consent status was updated because Implied - Inquiry consent does not apply to contacts in the EU. The status becomes Not Provided with the description Implied - Inquiry consent does not apply to contacts in the EU. On PATCH /v1/contacts/{id}, result_data also gets inapplicable_consent_updated: true and consent_fix_message holding the same sentence.

The type name in each sentence is the one that does not apply (Implied - Inquiry or Implied - Transaction).


  • Type: Array (read); array or object (write)
  • Read-only: No
  • Required on create: No
  • Returned value (read): Array of the names of every tag, subscription, and mandatory consent item the contact has.
  • Example: ["Volunteer", "Alumni", "Concert News"]

interests is the single field on the v1 Contact API for tags, subscriptions, and mandatory consent; there are no separate tags or subscriptions keys. The Tag API and Subscription API have dedicated endpoints for adding and removing contacts in bulk.

Form Example Effect
List of names ["Volunteer", "Concert News"] Adds each one. Nothing is removed.
Object of states { "Volunteer": "Set", "Alumni": "Unset" } "Set" or 1 adds; "Unset" or 0 removes.
  • Keys and names: matched by name, ignoring case and treating a space and an underscore as the same. In the object form, a key can also be the item’s numeric ID. The numeric states (1, 0) must be JSON numbers; a string such as "1" is read as a name.
  • Unknown name: Invalid 'interest': 'Volunteers' (for a list of valid interests try the endpoint '/interests')
  • Wrong type: 'interests' must be an object or array

Note: Adding a subscription does not check the contact’s sending consent. A contact whose consent is Not Provided is added to the subscription like any other contact.

  • Type: Array (read); array or object (write)
  • Read-only: No
  • Required on create: No
  • Returned value (read): Array of the names of the nurture campaigns the contact is on.
  • Example: ["Welcome Sequence"]
Form Example Effect
List of names ["Welcome Sequence"] Adds and queues each one.
Object of states { "Welcome Sequence": "Set" } "Set", 1, or 2 adds and queues.
{ "Welcome Sequence": "Unset" } "Unset" or 0 removes.
{ "Welcome Sequence": "Dequeue" } "Dequeue" or -1 stops further messages but leaves the contact on the campaign.
  • Keys and names: as for interests; an object key can also be the campaign’s numeric ID.
  • Unknown name: Invalid 'autoresponder': 'Welcome' (for a list of valid autoresponders try the endpoint '/autoresponders')
  • Wrong type: 'autoresponders' must be an object or array

Note: A campaign is not added (the contact is not put on it, and nothing is queued) when the contact’s consent does not allow sending, unless the campaign is marked transactional (and never for a spam-reported contact). The skipped campaign is not reported, and the response still reports success.

Nurture campaigns cannot be created through the API; create them in Envoke first.


Field On write On read In filters
date_created Ignored W3C in UTC, "2026-10-05T14:03:11+00:00" Read as UTC unless the value has a time zone
consent_effective_date Read as UTC unless the value has a time zone W3C in UTC Read as UTC unless the value has a time zone
consent_expiry Ignored "YYYY-MM-DD" or "" Compared as a plain YYYY-MM-DD date, no time zone conversion

Send times as 2026-09-12T18:30:00Z, 2026-09-12T14:30:00-04:00, or 2026-09-12 18:30:00 (UTC). A value with a date but no time is not converted from UTC; it is read as midnight in Envoke’s server time zone.


Validation errors (unknown fields, invalid values, missing required fields, bad filters):

{
"success": false,
"result_text": "Validation failed, see 'result_data' -> 'errors'",
"result_data": {
"errors": [
"'nickname' is not a valid contact field",
"Invalid 'consent_status': 'Opted In' (possible values: 'Express', 'Implied - Inquiry', 'Implied - Transaction', 'Implied - No Expiry', 'Not Provided', 'Revoked', 'Envoke Spam Reported')"
]
}
}

Field validation errors (field lengths, email format, duplicate check). These come from a second check that runs after the one above, and use a different shape: result_data.fields is keyed by database column name, and result_text holds the messages joined with , instead of the standard sentence.

{
"success": false,
"result_text": "Email invalid, First name must not exceed 50 characters",
"result_data": {
"fields": {
"email": "Email invalid",
"fname": "First name must not exceed 50 characters"
}
}
}

Most column names match the API field name. These differ:

API field Key in result_data.fields
first_name fname
last_name lname
address_1 addr1
address_2 addr2
postal_code postal
external_id eid

A duplicate-check failure has no per-field entry: result_data.fields is [] and result_text is contact with email = alto.lead@example.com already exists. On update, this envelope also carries a warnings key.

Empty or unreadable body. POST and PATCH with no body, an empty body, {}, or [] return {"success":false,"result_data":[],"result_text":"Invalid request"}. A body that is not valid JSON returns the same shape with result_text Invalid JSON.

All error responses above use HTTP status 400. An invalid API ID or key returns 401 with a WWW-Authenticate: Basic header and the body {"success":false,"result_data":[],"result_text":"Invalid credentials"} (sent with Content-Type: text/html). A valid key on a closed or inactive account returns 403 with the JSON body {"success":false,"result_data":[],"result_text":"This account has been closed."} (or "This account is not active.") Exceeding your account’s API limit returns 429 with success: false and a result_text naming the limit.

A country or language value that is not recognised does not fail the request. The field is left unchanged, the rest of the request is applied, and the response reports success with a warning such as 'Narnia' is not a valid value for country; field will not be updated. Where the warning appears depends on the endpoint:

Endpoint Where warnings appear
POST /v1/contacts (create) Appended to result_text after a space, joined with .
PATCH /v1/contacts/{id} result_data.warnings, an array of strings ([] when none)
PATCH /v1/contacts with an object body, and POST /v1/contacts?upsert=1 when it updates Appended to result_text after . , which follows the success message’s own period: The contact was successfully updated.. 'Narnia' is not a valid value for country; field will not be updated
PATCH /v1/contacts with a list body result_data["{id}"].warnings for each contact

Not-found behaviour varies by method:

  • GET /contacts/{id} — when the ID is not found, returns an empty array ([]).
  • PATCH /contacts/{id} — when the ID is not found, returns a validation error:
{
"success": false,
"result_text": "Validation failed, see 'result_data' -> 'errors'",
"result_data": {
"errors": ["No contact was found with ID: '999999'"]
}
}
  • PATCH /contacts (object body) — when no contact matches the key field, returns {"success":false,"result_data":[],"result_text":"No contact was found with the 'email': 'nobody@example.com'"}. A body id sent without key_field is not looked up first, so a missing ID gives the validation error shown for PATCH /contacts/{id}.
  • PATCH /contacts (list body) — the request returns HTTP 400 and the missing contact is reported in result_data under CONTACT_OBJECT_{n} (see Update).
  • DELETE /contacts/{id} — when the ID is not found, returns {"success":false,"result_text":"1 contact not deleted, 1 - no matching contact found"} with no result_data key.

Full reference, OpenAPI description, and Postman collection: developers.envoke.com.