Add a contact
Adds a contact, or finds the one that already has this email, and records their consent when you say they gave it.
POST /v1/contacts
Example
curl https://api.wakio.net/v1/contacts \
-H "Authorization: Bearer $WAKIO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"email": "[email protected]",
"first_name": "Maya",
"last_name": "Okafor",
"external_id": "user_8812",
"subscribed": true,
"consented_at": "2026-09-30T14:02:00Z"
}'
Response 201:
{
"id": 41403,
"status": "active"
}
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
email | string | Yes | Matched ignoring case. An existing contact keeps its names, external_id and source; only subscribed can change it. |
first_name | string | No | First name. |
last_name | string | No | Last name. |
external_id | string | No | Your own id for this person, unique within your account. A contact is found by email, never by external_id. |
source | string | No | A free-text label for where the contact came from, such as signup-form. |
subscribed | boolean | No | true says this person agreed to receive your campaigns: Wakio writes a consent record and the contact becomes active. Left out or false, a new contact is never_subscribed, and no campaign or flow reaches it. An address on your suppressed list is the exception either way, and gets no consent record. false never unsubscribes anyone. A JSON boolean, not a string. |
consented_at | string | No | When they agreed, as an ISO 8601 time with its offset, such as 2026-09-30T14:02:00Z or 2026-09-30T16:02:00+02:00, no later than 5 minutes from now. Wakio refuses a time with no offset. Only with subscribed: true. Left out, the consent record carries the time of the request. |
consent_ip | string | No | The IPv4 or IPv6 address they agreed from. Only with subscribed: true. |
Response
| Name | Type | Required | Description |
|---|---|---|---|
id | integer | Yes | The contact's id. The status is 201 when it was created and 200 when it already existed. |
status | string | Yes | The contact's status after this request: one of active, unsubscribed, unreachable, or never_subscribed. Only active contacts receive campaigns and flows. |
Errors
Every error this endpoint itself returns is {"error": "<code>"}, sometimes with one more field that explains it. The API overview lists the responses that do not follow that shape.
| Status | Code | What happened | What to do |
|---|---|---|---|
401 | invalid_api_key | The Authorization header is missing, malformed, or names no active key. | Send Authorization: Bearer <key> with an active key from API keys in the dashboard. |
422 | invalid_contact | The contact or its consent fields failed validation. detail lists each problem. | Fix each problem detail lists. |
422 | external_id_taken | Another contact in this account already has this external_id, so nothing was created. external_id echoes it. | Send this person's own id, or leave external_id out. |
Notes
Sending the same email twice is safe. The second call answers 200 with the same id. It never changes the contact's names, external_id or source, so this endpoint cannot update a contact.
Without subscribed: true, a new contact is never_subscribed. Campaigns and flows leave out anyone who is not active, so Wakio stores the contact and sends it nothing except the email your app sends through Send an email.
With subscribed: true, you are telling Wakio the person agreed to receive your campaigns. Wakio makes the contact active and writes a consent record with the basis Controller assertion and the source API, dated consented_at or the time of the request. Wakio records your word, not proof of it.
An address already on your suppressed list is the exception, whatever the request says. A new contact for it starts out unreachable when the reason is Mailbox does not exist or Address not valid, and unsubscribed when the reason is Marked as spam, Unsubscribed, or Added by your team. A contact that already exists keeps the status it has. Either way, Wakio writes no consent record.
On a contact that already exists, subscribed: true changes only a never_subscribed contact that is not on your suppressed list, which becomes active. It never makes an unsubscribed or unreachable contact active again. Read status in the response to see which happened. subscribed: false never unsubscribes anyone: a person leaves your list by unsubscribing.