All routes start with /wp-json/hivemail/v1. Sign in as described in authentication; errors and paging are on the overview. Capability letters: V hivemail_view_audience, A hivemail_manage_audience, F hivemail_manage_automations, I hivemail_view_intelligence, S hivemail_manage_settings. Routes that accept PUT also accept POST and PATCH.
Flows and runs #
All need F, except a contact's flows (V). A flow is a graph { version: 1, nodes[], edges[] }. A node is { id, type, props, position: {x, y}, label? }; an edge is { from, to, port } (out for most steps, yes/no for a condition, a/b for a split, met/not_met for a wait and a goal). States: draft, active, paused. A flow with people in it cannot be deleted (409 hivemail_flow_in_use).
| Method | Route | Purpose |
|---|---|---|
| GET | /flows |
status, search, orderby, order, page, per_page (max 100). Items carry run counts, no graph |
| POST | /flows |
name (required), description, graph, settings. 201, a draft with problems[] and ready |
| GET | /flows/catalogue |
The node types, triggers and fields the canvas is drawn from |
| POST | /flows/validate |
graph in; { graph (cleaned), problems[], ready } out. Saves nothing |
| GET | /flows/{id} |
The flow with etag, graph, problems, runs |
| PUT | /flows/{id} |
name, description, graph, settings, etag. A stale etag is 409 hivemail_flow_conflict carrying the saved flow |
| DELETE | /flows/{id} |
Removes it with its runs and log |
| POST | /flows/{id}/activate |
409 hivemail_flow_not_ready with data.problems when something blocks it |
| POST | /flows/{id}/pause, /stop |
Pause (nobody new, nobody moves) or stop (every open run ends; the flow is a draft) |
| POST | /flows/{id}/duplicate |
201 |
| POST | /flows/{id}/enrol |
{ contact_id } answers { run_id, enrolled }; { audience } (the campaign audience format) answers 202 with a background job. 409 while the flow is off |
| POST | /flows/{id}/test |
contact_id. The path a dry run takes; nothing is done |
| GET | /flows/{id}/report |
People, steps reached, email numbers per step, attributed revenue |
| GET | /flows/{id}/runs |
status (open or a run state), node, page, per_page |
| GET | /flows/{id}/runs/{run_id} |
One run with its step log |
| POST | /flows/{id}/runs/{run_id}/stop |
End one run |
| GET | /runs |
Every flow's runs: flow, contact, status, page, per_page |
| GET | /contacts/{id}/flows |
One contact's runs (V) |
# Validate a graph: when someone subscribes, add tag 7
curl -u "admin:abcd efgh ijkl mnop qrst uvwx" -X POST -H "Content-Type: application/json" \
-d '{"graph":{"version":1,"nodes":[
{"id":"trigger","type":"trigger","props":{"trigger":"subscribed","settings":{}},"position":{"x":0,"y":0}},
{"id":"tag","type":"tag","props":{"op":"add","id":7},"position":{"x":0,"y":180}}],
"edges":[{"from":"trigger","to":"tag","port":"out"}]}}' \
"https://example.com/wp-json/hivemail/v1/flows/validate"
# {"graph":{...},"problems":[],"ready":true}
Use the same body with "name" on POST /flows, then POST /flows/{id}/activate. To add everyone on list 1 to an active flow:
await fetch( `https://example.com/wp-json/hivemail/v1/flows/${ id }/enrol`, {
method: 'POST',
headers: { Authorization: auth, 'Content-Type': 'application/json' },
body: JSON.stringify( { audience: { lists: [ 1 ] } } ), // 202 { job }; follow it on /jobs/{id}
} );
Enrolling twice enrols nobody twice (the second answer is { "run_id": 0, "enrolled": false }); the flow's re-entry setting decides the rest. Deleting a flow that still has people in it is 409 hivemail_flow_in_use: stop it first.
Recipes #
| Method | Route | Cap | Purpose |
|---|---|---|---|
| GET | /recipes |
F | Ready-made flows with their steps in words, what they create, and available (false with a reason when a plugin they need is missing) |
| POST | /recipes/{id}/install |
F | name optional. 201 { flow, created, reused, placeholders }. The flow is a draft. 404 hivemail_recipe_not_found; 409 hivemail_recipe_unavailable |
Install makes only what is missing (lists, tags, fields, segments, email templates by name) and never switches the flow on.
Incoming webhook trigger #
A flow whose trigger is An incoming webhook arrives has an address that is not under /wp-json. Two REST routes manage it (both F):
| Method | Route | Purpose |
|---|---|---|
| GET | /flows/{id}/webhook |
{ url, secret, header, enabled }. Made on first use |
| POST | /flows/{id}/webhook/rotate |
New address and secret; the old ones stop at once |
Posting to the address, signing the body and the answers are on Webhooks and API keys.
Signup forms #
| Method | Route | Cap | Purpose |
|---|---|---|---|
| GET | /forms |
A | List, with submissions, confirmed, shortcode |
| POST | /forms |
A | title and settings (at least list_ids, the lists people join). 201 with a Location header. 400 hivemail_invalid_form with errors{ key: message } |
| GET | /forms/captcha |
A | { provider, label, ready } (never a key) |
| POST | /forms/preview |
A | Unsaved settings in; { html, css_url } out |
| GET | /forms/{id} |
A | One form |
| PUT | /forms/{id} |
A | Send version to guard against two people saving at once: a stale one is 409 hivemail_form_conflict (data.version is the current one) |
| DELETE | /forms/{id} |
A | |
| POST | /forms/{id}/duplicate |
A | 201, an off copy |
| GET | /forms/{id}/preview |
A | The rendered HTML |
| POST | /forms/{id}/submit |
public | A signup. Keys email, first_name, last_name, consent, website (honeypot, leave empty), t (the time token), captcha |
Submit answers 200 { ok: true, message } for every valid outcome and for a robot, so the form cannot reveal who is subscribed. Errors are { ok: false, code, message }: 400 (email, name, consent, captcha, expired), 404 (closed), 429 (limit: 3 a minute, 10 an hour per visitor). The form needs a signed time token that is 3 seconds to 24 hours old, so build forms with the shortcode or block rather than posting by hand.
Connected forms (other form plugins) #
The plugin keys are fluentforms, wpforms, cf7, gravityforms and elementor.
| Method | Route | Cap | Purpose |
|---|---|---|---|
| GET | /integrations |
S | What was found on this site, and how many flows use each trigger |
| GET | /integrations/{key}/forms |
F | { supported, items[{id, title}] } |
| GET | /integrations/{key}/forms/{form_id}/fields |
F | The form's fields where the plugin can list them |
| POST, GET, DELETE | /integrations/{key}/capture |
F | Start, read or stop "show me what the next submission sends" (10 minutes) |
| POST | /integrations/connect |
F | key, form_id, form_title, mapping, consent_field, lists[], tags[], double_optin. Makes one inactive flow "Form: {title}". 409 if the form is already connected |
| GET | /integrations/connected |
F | The connected forms |
| DELETE | /integrations/connected/{flow_id} |
F | Pauses the flow and removes the connection; the flow stays |
Shops #
{shop} is woocommerce or edd. A shop that is not active answers 404 hivemail_shop_missing; edd has no carts (404 hivemail_shop_no_carts).
| Method | Route | Cap | Purpose |
|---|---|---|---|
| GET | /commerce/{shop}/carts |
V | status (open, abandoned, recovered, ordered), search, page, per_page (max 100) |
| GET | /commerce/{shop}/carts/{id} |
V | One cart with restore_url |
| DELETE | /commerce/{shop}/carts/{id} |
A | Forget a cart; its restore link stops working |
| GET | /commerce/{shop}/customers |
V | search, orderby (last_order_at, total_spent, orders_count, first_order_at, email, name), order, page, per_page |
| GET | /commerce/{shop}/index |
V | The customer index summary and its last rebuild |
| POST | /commerce/{shop}/index/rebuild |
S | 202 { job }: recompute every customer |
| GET | /commerce/lookup/{kind} |
V, F or C | search or ids. Kinds woo_product, woo_category, edd_download, edd_category (404 hivemail_lookup_unknown for another kind) |
curl -u "admin:abcd efgh ijkl mnop qrst uvwx" \
"https://example.com/wp-json/hivemail/v1/commerce/woocommerce/carts?status=abandoned&per_page=10"
Visitor reports and journeys #
All need I. range is today, 7, 30 or 90 (days, in the site time zone; default 30). With tracking off they still answer 200 with zeros.
| Method | Route | Purpose |
|---|---|---|
| GET | /intelligence/overview |
Visitors, sessions, pageviews, conversions, revenue, a timeline |
| GET | /intelligence/live |
The last 5 minutes: visitors now, pages, a feed. Poll every 5 to 10 s |
| GET | /intelligence/pages |
range, search, orderby, order, page, per_page (max 100) |
| GET | /intelligence/pages/{id} |
One page: totals, timeline, top sources |
| DELETE | /intelligence/pages/{id} |
Forget a page and its hits (S) |
| GET | /intelligence/sources |
range, model (last, first), by (channel, source, campaign) |
| GET | /intelligence/conversions |
Goals, conversions, revenue by channel |
| GET | /intelligence/goals |
The goals (up to 20) |
| PUT | /intelligence/goals |
Replace the goals (S) |
| GET | /intelligence/consent |
The consent tools found on the site (S) |
| GET | /intelligence/visitors |
range, identified (all, yes, no), search, page, per_page |
| GET | /intelligence/visitors/{id} |
One visitor's visits |
| GET | /contacts/{id}/journey |
Needs I and V |
Money is a list per currency ([{ currency, amount }]), never added across currencies. GET /intelligence/goals and GET /integrations/connected answer a bare list, not an object.
By name only #
The assistant (/ai/*, capability hivemail_use_ai), Migration from FluentCRM (/migration/*), Check my list (/email-checks), Roles (/roles/*) and API keys (/api-keys/*) are registered under the same namespace and follow the same rules. They are described on their own screens' pages.
Next: Webhooks and API keys, Hooks: actions.