Create a contact
/api/v1/contactsChecks. Once Bounce protection is on for your account, a new contact, or a contact whose address
changes, is checked in the background with one check: its email_status reads pending until the
answer lands, then valid, risky, invalid (and the contact becomes bounced) or unknown. With
Bounce protection switched off, or no checks left, the contact is still saved, as not_checked. A
contact from an event (unconfirmed) is checked when it becomes subscribed. The write never fails
because of a check.
Send "check": "wait" to have the address checked before we answer (up to about 8 seconds), so the
email_status in the reply is already the answer. Any other value is ignored. If the checker is
slow, the contact is saved and checked in the background; if no checks are left, it is saved as
not_checked.
The reply carries X-Mailyte-Checks-Remaining and, when it applies, X-Mailyte-Checks-Warning: running_low | ran_out. Neither is sent until Bounce protection is on for your account.
Request body
emailstringrequiredTheir email address. If it is already one of your contacts, nothing about them changes: they are only added to
list_ids.namestringThe person's name, as it should appear in your emails (
{{name}}).attributesobjectCustom fields as key–value pairs, for example
{"city": "Lagos", "plan": "pro"}. Use the keys fromGET /contact-fields.statusstringsubscribed | unsubscribed | bounced | complainedDefaults to
subscribed. Bring someone over asunsubscribed,bouncedorcomplainedand they are never emailed by mistake.sourcestringOptional. Where this contact came from, in your own words (up to 50 characters), like
checkout.consented_atstringWhen this person agreed to be emailed, in ISO 8601. Bring it when you move from another service: it is your proof of consent.
consent_ipstringThe IP address the agreement came from. IPv4 or IPv6.
consent_sourcestringWhere they agreed, in your own words: a form name, a checkout step, "imported from Mailchimp".
list_idsarray<string>Lists to put the contact on. Copy IDs from Contacts → Lists in the dashboard, or read them from
GET /contact-lists.checkstringSend
"wait"to have the address checked before we answer (up to about 8 seconds), soemail_statusin the reply is already the result. Any other value is ignored.
Request
/api/v1/contactscurl -X POST 'https://app.mailyte.com/api/v1/contacts' \
-H 'Authorization: Bearer mk_live_YOUR_API_KEY' \
-H 'Content-Type: application/json' \
-d '{
"email": "ada@example.com",
"name": "Ada Obi",
"list_ids": [
"01JBT8XQ2M9WYC3K4F6R7S8T9V"
]
}'const response = await fetch('https://app.mailyte.com/api/v1/contacts', {
method: 'POST',
headers: {
Authorization: 'Bearer mk_live_YOUR_API_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({
"email": "ada@example.com",
"name": "Ada Obi",
"list_ids": [
"01JBT8XQ2M9WYC3K4F6R7S8T9V"
]
}),
});
const { data } = await response.json();import requests
response = requests.post(
"https://app.mailyte.com/api/v1/contacts",
headers={"Authorization": "Bearer mk_live_YOUR_API_KEY"},
json={
"email": "ada@example.com",
"name": "Ada Obi",
"list_ids": [
"01JBT8XQ2M9WYC3K4F6R7S8T9V"
]
},
)
data = response.json()["data"]<?php
$response = Http::withToken('mk_live_YOUR_API_KEY')
->post('https://app.mailyte.com/api/v1/contacts', [
'email' => 'ada@example.com',
'name' => 'Ada Obi',
'list_ids' => [
'01JBT8XQ2M9WYC3K4F6R7S8T9V',
],
]);
$data = $response->json('data');require "net/http"
require "json"
uri = URI("https://app.mailyte.com/api/v1/contacts")
request = Net::HTTP::Post.new(uri)
request["Authorization"] = "Bearer mk_live_YOUR_API_KEY"
request["Content-Type"] = "application/json"
request.body = {
"email": "ada@example.com",
"name": "Ada Obi",
"list_ids": [
"01JBT8XQ2M9WYC3K4F6R7S8T9V"
]
}.to_json
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(request) }Response
Success.
dataobjectobjectstringcontactidstringUnique identifier for the contact.
emailstringLower-cased and trimmed on write, so the address you read back may not be byte-identical to the one you sent.
namestringThe contact's name. Null when none was given;
{{ name }}is then empty unless your template gives a fallback.statestringsubscribed | unsubscribed | bounced | complained | suppressedWhether this contact will be emailed.
subscribedmeans yes; any other value is the reason not. It combines the contact'sstatuswith your suppression list, so a subscribed contact whose address is suppressed readssuppressed. Read-only: you setstatus(subscribed,unsubscribed,bouncedorcomplained) instead. Note that?status=filters onstatus, not on this.email_statusstringpending | valid | risky | invalid | unknown | not_checkedThe result of checking whether the mailbox exists.
valid: it exists.invalid: it does not.risky: it may not take mail (a catch-all domain, a role address such as info@, a throwaway address or a full inbox).unknown: the check could not tell.pending: being checked.not_checked: added after Bounce protection launched and not checked. Null: never checked, from before Bounce protection launched. Read-only; filter with?email_status=.email_full_untilstringUntil when marketing to this contact is held because their inbox is full. When a check finds the inbox full, marketing waits 7 days; near the end it is checked again for free and the hold either ends or runs another 7 days. App email is never held. Null means no hold. Read-only.
fieldsobjectCustom field values, keyed by the field's
key(the name{{ signup_date }}uses). Always an object,{}when empty. Values are strings as stored; field defaults are not filled in here. When writing, send these asattributes.listsarray<object>The lists this contact is on, with their names. When writing, send list IDs as
list_ids.idstringnamestring
tagsarray<object>Your labels on this contact, with their names.
idstringnamestring
interestsarray<object>What this contact chose on a form, each with the question it answers.
[]means they chose nothing.idstringMatch on this in a segment rule, never on the name.
namestringThe answer, as the contact read it.
category_idstringThe question this answers.
category_titlestringThe question in words.
nullMEANS NOT INCLUDED — the category was not loaded, never that the question has no title.
consentobjectHow this contact agreed to be emailed. Each field is null when we have no record. Set it when you import from another service, so your proof of consent comes with your contacts.
atstringWhen they agreed. Null when unknown.
ipstringThe IP address they agreed from. Null when unknown.
sourcestringWhere they agreed, such as a form name, a URL or "imported from Mailchimp". Null when unknown.
sourcestringHow this contact was added:
manual,import_api,import_csvor your own text (up to 50 characters). Null when nothing was recorded.created_atstringupdated_atstring
Returned inside the standard envelope.
Errors
| Status | When |
|---|---|
401 | The API key is missing, unknown, revoked or expired. All four answer identically, on purpose: distinguishing them would confirm which keys exist. |
403 | The key is valid but may not do this: it lacks the required scope, its IP allowlist does not include you, or this endpoint does not accept API keys. |
404 | No such resource in this organization. |
422 | The request was understood but the values were not acceptable. |
429 | Too many requests, or the organization has spent its sending allowance. `Retry-After` says how long to wait. |
Every status, with what causes it and what to do, is on the error reference.