All routes start with /wp-json/hivemail/v1. Sign in as described in authentication; errors, paging and the three basic requests are on the overview. In the tables V is hivemail_view_audience and A is hivemail_manage_audience. Routes that accept PUT also accept POST and PATCH.
Every contact answer carries fields ({ slug: typed value }, only fields with a value), lists ([{id, name, status}], where status is the status on that list) and tags ([{id, name, color}]).
Contacts #
Statuses you can set: pending, subscribed, unsubscribed, transactional. bounced and complained are set by Hive Mail only. Subscribing needs consent: true. Deleting moves a contact to the trash for 30 days.
| Method | Route | Cap | Purpose |
|---|---|---|---|
| GET | /contacts |
V | List. page, per_page (max 500), search (2+ characters), status, list, tag, segment, source, created_from, created_to, orderby (id, email, first_name, last_name, status, created_at, updated_at, subscribed_at), order, trashed |
| POST | /contacts |
A | Create. email (required, 190 characters), first_name, last_name, phone, country (2 letters), company, source, status, consent, lists[], tags[] (ids), fields ({ slug: value }). Everything is validated before anything is written |
| GET | /contacts/stats |
V | Counts per status, plus all and trashed |
| POST | /contacts/bulk |
A | action (trash, restore, delete_permanently, set_status, add_to_list, remove_from_list, add_tag, remove_tag), ids[] (up to 1,000), and status, consent, reason, list_id, tag_id as the action needs. Answers { action, requested, succeeded, failed, errors[], details } |
| GET | /contacts/{id} |
V | One contact, trashed ones too. Adds allowed_statuses (legal next statuses) |
| PUT | /contacts/{id} |
A | Change fields, fields (null or "" clears a value) and/or status. All or nothing: an illegal status or an invalid custom value writes nothing |
| DELETE | /contacts/{id} |
A | Move to the trash. ?permanent=1 deletes a trashed contact for good (409 otherwise) |
| POST | /contacts/{id}/status |
A | status, consent, permission_given, reason, note |
| POST | /contacts/{id}/memberships |
A | add_lists[], remove_lists[], add_tags[], remove_tags[] (ids). Answers the contact; unknown ids are 404 |
| POST | /contacts/{id}/reactivate |
A | Give a bounced address another chance |
| POST | /contacts/{id}/restore |
A | Restore from the trash |
| POST | /contacts/{id}/send-confirmation |
A | list_id optional. Send the double opt-in email to a pending contact. 202 { message_id }; 409 if not pending or suppressed; 429 if one went out in the last 5 minutes |
| GET | /contacts/{id}/timeline |
V | limit, before. Answers { items, next_cursor } |
| GET | /contacts/{id}/segments |
V | The segments the contact is in |
| GET | /contacts/{id}/campaigns |
V | The campaign emails the contact received |
| GET | /contacts/{id}/flows |
V | The contact's flow runs |
| GET | /contacts/{id}/journey |
V and visitor reports | The contact's visits and timeline, merged (visitor routes) |
Error codes: hivemail_invalid_contact (400), hivemail_invalid_fields (400, errors["fields.{slug}"]), hivemail_consent_required (400), hivemail_invalid_status (400), hivemail_status_not_allowed and hivemail_invalid_transition (400), hivemail_list_not_found and hivemail_tag_not_found (400 on create, 404 on memberships), hivemail_contact_not_found (404), hivemail_contact_exists and hivemail_contact_suppressed (409).
Update, change status, bulk #
# Change the name and set a custom field with the slug "plan"
curl -u "admin:abcd efgh ijkl mnop qrst uvwx" -X PUT -H "Content-Type: application/json" \
-d '{"first_name":"Ada","fields":{"plan":"Starter"}}' \
"https://example.com/wp-json/hivemail/v1/contacts/42"
# Unsubscribe
curl -u "admin:abcd efgh ijkl mnop qrst uvwx" -X POST -H "Content-Type: application/json" \
-d '{"status":"unsubscribed","reason":"asked by email"}' \
"https://example.com/wp-json/hivemail/v1/contacts/42/status"
# Tag 3 contacts at once
curl -u "admin:abcd efgh ijkl mnop qrst uvwx" -X POST -H "Content-Type: application/json" \
-d '{"action":"add_tag","ids":[41,42,43],"tag_id":7}' \
"https://example.com/wp-json/hivemail/v1/contacts/bulk"
# {"action":"add_tag","requested":3,"succeeded":3,"failed":0,"errors":[],"details":{"added":1,"existing":2,"missing":0}}
From PHP #
<?php
$response = wp_remote_request(
'https://example.com/wp-json/hivemail/v1/contacts',
array(
'method' => 'POST',
'headers' => array(
'Authorization' => 'Basic ' . base64_encode( 'admin:abcd efgh ijkl mnop qrst uvwx' ),
'Content-Type' => 'application/json',
),
'body' => wp_json_encode( array( 'email' => 'ada@example.com', 'first_name' => 'Ada', 'consent' => true, 'lists' => array( 1 ) ) ),
'timeout' => 20,
)
);
$code = wp_remote_retrieve_response_code( $response ); // 201 created, 409 already a contact
Lists #
A contact's membership of a list has its own status. Adding someone who left a list does not resubscribe them. Names are unique, case-insensitive (409 hivemail_list_exists with existing_id).
| Method | Route | Cap | Purpose |
|---|---|---|---|
| GET | /lists |
V | search, orderby (id, name, created_at, updated_at), order, page, per_page (max 100). Items carry member_count, unsubscribed_count |
| POST | /lists |
A | name (150), description (500), is_public. Answers 201 |
| GET | /lists/{id} |
V | One list |
| PUT | /lists/{id} |
A | Change it |
| DELETE | /lists/{id} |
A | Answers { removed_members }. The contacts stay |
curl -u "admin:abcd efgh ijkl mnop qrst uvwx" -X POST -H "Content-Type: application/json" \
-d '{"name":"Newsletter","is_public":true}' \
"https://example.com/wp-json/hivemail/v1/lists"
Tags #
| Method | Route | Cap | Purpose |
|---|---|---|---|
| GET | /tags |
V | Same query arguments as lists. Items carry contact_count, color |
| POST | /tags |
A | name, color (#rrggbb or empty), find_or_create. With find_or_create an existing name answers 200, a new one 201; without it an existing name is 409 hivemail_tag_exists |
| GET | /tags/{id} |
V | One tag |
| PUT | /tags/{id} |
A | Change it |
| DELETE | /tags/{id} |
A | Answers { removed_assignments } |
Custom fields #
Types: text (255), textarea (5,000), number, date (YYYY-MM-DD), select, multiselect, checkbox. A field's slug is its merge tag and its key in fields. It matches ^[a-z][a-z0-9_]{0,63}$, cannot be a reserved key (email, first_name, …), and cannot change after creation, like the type. Values come back typed (number as a number, checkbox as a boolean, multiselect as an array). A bad value in a write is 400 with errors["fields.{slug}"], and nothing is written.
| Method | Route | Cap | Purpose |
|---|---|---|---|
| GET | /fields |
V | { items, total, types } in display order. Items carry merge_tag, value_count, options |
| POST | /fields |
A | label (required), type, options[], slug (default: made from the label, with _2, _3 added when taken). 409 hivemail_field_exists when you send a slug that is taken; 400 hivemail_invalid_field for a reserved one |
| POST | /fields/reorder |
A | ids[]: every field exactly once |
| GET | /fields/{id} |
V | One field |
| PUT | /fields/{id} |
A | label and options only |
| DELETE | /fields/{id} |
A | Also removes the stored values. Answers { removed_values } |
// Create a "Plan" dropdown (slug: plan)
await fetch( 'https://example.com/wp-json/hivemail/v1/fields', {
method: 'POST',
headers: { Authorization: auth, 'Content-Type': 'application/json' },
body: JSON.stringify( { label: 'Plan', type: 'select', options: [ 'Starter', 'Team' ] } ),
} );
Import, export and background jobs #
Everything here needs A. Long work runs as a job: staged (an uploaded file waiting for a mapping), queued, running, then completed, failed or cancelled. The minute tick works jobs, and POST /jobs/{id}/step works one slice at once.
| Method | Route | Purpose |
|---|---|---|
| GET | /import/options |
Mapping targets (email, first_name, …, field:{slug}), upload size limit, WordPress roles |
| POST | /import/upload |
Multipart file (.csv, .tsv, .txt). Answers a staged job whose info has headers, sample, rows, delimiter, suggested |
| POST | /import/{id}/preview |
mapping[] (one entry per column), options{}. A dry run of the first 200 rows; writes nothing |
| POST | /import/{id}/start |
Same body. Queues the import and answers the job (status: "queued") |
| POST | /import/wp-users |
roles[], options{}. Queues "import WordPress users" |
| POST | /export |
status, list, tag, search, trashed. Queues a CSV export (all contacts when empty). 201, the job |
| GET | /jobs |
Recent jobs, ?type=import|wp_users|export |
| GET | /jobs/{id} |
{ status, total, processed, percent, result, error, info, downloadable } |
| POST | /jobs/{id}/step |
Work one slice now |
| POST | /jobs/{id}/cancel |
Stop a queued or running job |
| DELETE | /jobs/{id} |
Delete a finished job and its files (409 while running) |
| GET | /jobs/{id}/download |
The export, or an import's error report (authenticated, never a public address) |
| GET | /migrate/sources |
The importable plugins and what each holds |
| POST | /migrate/{source}/preview |
options in, dry run and plan out |
| POST | /migrate/{source}/start |
options in. Queues the copy (201) |
| PUT, DELETE | /migrate/{source}/sync |
Switch live sync of new customers on or off (edd, woocommerce, affiliatewp) |
mapping has one entry per CSV column: "" (ignore), email, first_name, last_name, full_name, phone, country, company, status or field:{slug}. Exactly one column is email. Import options: duplicates (skip, update or fill), lists[], tags[] (ids), consent (you attest the people gave permission: new contacts are subscribed, otherwise pending). An import never changes the status of an existing contact.
# 1. Upload (answers a staged job, say id 15)
curl -u "admin:abcd efgh ijkl mnop qrst uvwx" -F "file=@people.csv" \
"https://example.com/wp-json/hivemail/v1/import/upload"
# 2. Start it (email in column 0, first name in column 1)
curl -u "admin:abcd efgh ijkl mnop qrst uvwx" -X POST -H "Content-Type: application/json" \
-d '{"mapping":["email","first_name"],"options":{"duplicates":"skip","lists":[1],"consent":false}}' \
"https://example.com/wp-json/hivemail/v1/import/15/start"
# 3. Follow it
curl -u "admin:abcd efgh ijkl mnop qrst uvwx" "https://example.com/wp-json/hivemail/v1/jobs/15"
When it is done, status is completed and result reads {"created":2,"updated":0,"skipped":0,"failed":0,"failures":[]}. Contacts made without consent are pending.
Next: Campaigns, segments, templates and reports, Database tables.