MBX Reach

MBX Reach · Developer reference

REST API documentation

Authentication and every API endpoint used by the MBX Reach Zapier integration.

Base URL: https://mbxreach.com/api/v1 · API version: v1
Maintained by MediaBoxEnt Technologies · Updated November 3, 2026

Authentication

API access requires Starter or higher. Create a workspace API key in Integrations → API keys. Only that settings page requires login; this reference is public. A key is bound to one workspace. Send it on every request using HTTPS:

Authorization: Bearer YOUR_MBX_REACH_API_KEY
Accept: application/json
Content-Type: application/json

Keys begin with mbxm_. Keep the full value secret. Expired or revoked keys must be replaced and the Zapier connection reconnected. An IP allowlist, if configured on the key, must allow the caller. JSON bodies are required for the POST endpoints below except unsubscribe, which takes no body.

For all Zapier triggers, actions, searches and dropdowns, enable contacts:read, contacts:write, campaigns:read and webhooks:manage. contacts:write includes contacts:add. The integration does not require campaign sending permission.

curl https://mbxreach.com/api/v1/me \
  -H "Authorization: Bearer YOUR_MBX_REACH_API_KEY" \
  -H "Accept: application/json"

Success responses normally wrap resources in data; /me is the exception. Use the raw API response shapes below, rather than Zapier’s flattened output fields. ISO 8601 timestamps are UTC. URL-encode email addresses and path identifiers.

GET /me

Required scope: Any valid API key

Zapier uses this to test authentication and display the workspace name. No request parameters.

Example response · 200

Illustrative data; identifiers and timestamps vary.

{
  "workspace": {
    "id": "ws_example",
    "name": "Example workspace",
    "plan": "starter"
  },
  "key": {
    "id": "key_example",
    "name": "Zapier",
    "scopes": [
      "contacts:read",
      "contacts:write",
      "campaigns:read",
      "webhooks:manage"
    ]
  },
  "sender_domains": [
    "mail.example.com"
  ]
}

GET /contacts

Required scope: contacts:read

For an exact email lookup, send ?email=alex%40example.com. Returns {data: contact}, or 404 if absent. A blank email returns 422. Without email, returns an array, newest first. limit: 1–200 (default 50); offset: default 0, maximum 10000. For longer traversals, pass the returned next_cursor as cursor on the next request, preserving limit. Stop when next_cursor is null. Zapier uses this endpoint for Find Contact, resolving unsubscribe IDs, and trigger samples.

Example response · 200

Illustrative data; identifiers and timestamps vary.

{
  "data": [
    {
      "id": "con_example",
      "email": "alex@example.com",
      "first_name": "Alex",
      "last_name": "Rivera",
      "fields": {},
      "status": "subscribed",
      "source": "zapier",
      "timezone": "America/New_York",
      "preferred_language": "en",
      "tags": [
        "newsletter"
      ],
      "lists": [
        {
          "id": "lst_example",
          "name": "Newsletter"
        }
      ],
      "created_at": "2026-11-03T12:00:00.000Z",
      "updated_at": "2026-11-03T12:00:00.000Z"
    }
  ],
  "limit": 50,
  "offset": 0,
  "next_cursor": null
}

POST /contacts

Required scope: contacts:add or contacts:write

Add or update a contact by required email. Optional fields: first_name, last_name (100 characters each), fields (object keyed by custom field name), tags (array of names), lists (array of list IDs), timezone, source (40 characters), status (pending or subscribed), resubscribe (boolean), preferred_language, and unverified (boolean). Tags and list memberships are added; tags do not replace existing tags. Zapier’s Add Tags action calls this same endpoint with email and tags, and can create a contact if missing.

Returns 201 for a new contact, 200 for an existing one. Inspect refused, contact status and awaiting_confirmation; HTTP success does not override suppression or consent requirements. Double-opt-in lists can send confirmation email and defer membership. Only request resubscription with renewed consent; permanent suppression is not bypassed. unknown_lists reports unrecognized IDs. With unverified:true, awaiting_confirmation is a boolean; otherwise it is an array of list IDs. Add-only keys receive a reduced contact response.

Example request body

{
  "email": "alex@example.com",
  "first_name": "Alex",
  "last_name": "Rivera",
  "tags": [
    "newsletter"
  ],
  "source": "zapier"
}

Example response · 201

Illustrative data; identifiers and timestamps vary.

{
  "data": {
    "id": "con_example",
    "email": "alex@example.com",
    "first_name": "Alex",
    "last_name": "Rivera",
    "fields": {},
    "status": "subscribed",
    "source": "zapier",
    "timezone": "America/New_York",
    "preferred_language": "en",
    "tags": [
      "newsletter"
    ],
    "lists": [
      {
        "id": "lst_example",
        "name": "Newsletter"
      }
    ],
    "created_at": "2026-11-03T12:00:00.000Z",
    "updated_at": "2026-11-03T12:00:00.000Z"
  },
  "created": true,
  "refused": null,
  "awaiting_confirmation": [],
  "unknown_lists": []
}

GET /lists

Required scope: contacts:read

Lists in the connected workspace, used by Zapier’s list dropdown. No pagination or request body.

Example response · 200

Illustrative data; identifiers and timestamps vary.

{
  "data": [
    {
      "id": "lst_example",
      "name": "Newsletter",
      "description": "Newsletter subscribers",
      "double_opt_in": true,
      "subscribed": 10,
      "created_at": "2026-11-03T12:00:00.000Z"
    }
  ]
}

GET /tags

Required scope: contacts:read

Available tags, used by Zapier’s tag dropdown. Optional limit: 1–200 (default 100); offset: 0–1000000 (default 0). Advance offset by limit until offset plus returned item count reaches total. Zapier requests limit=200.

Example response · 200

Illustrative data; identifiers and timestamps vary.

{
  "data": [
    {
      "id": "tag_example",
      "name": "newsletter",
      "contacts": 10
    }
  ],
  "limit": 200,
  "offset": 0,
  "total": 1
}

POST /lists/{list_id}/subscribe

Required scope: contacts:add or contacts:write

Path: existing workspace list ID. Required body: email. Optional: first_name, last_name, fields, tags, source, timezone, preferred_language, resubscribe, skip_confirmation and unverified. Boolean flags default to false. Double-opt-in can send a confirmation email; the workspace must have a valid verified sender. skip_confirmation:true is for contacts whose consent was already collected; it does not bypass permanent suppression and is ignored for unverified submissions. A missing list returns 404. Inspect returned contact status and refused before treating the contact as subscribed. A confirmation sender problem returns 422; a contact may already have been created before that error.

Example request body

{
  "email": "alex@example.com",
  "first_name": "Alex",
  "skip_confirmation": false,
  "source": "zapier"
}

Example response · 201

Illustrative data; identifiers and timestamps vary.

{
  "data": {
    "id": "con_example",
    "email": "alex@example.com",
    "first_name": "Alex",
    "last_name": "Rivera",
    "fields": {},
    "status": "pending",
    "source": "zapier",
    "timezone": "America/New_York",
    "preferred_language": "en",
    "tags": [
      "newsletter"
    ],
    "lists": [],
    "created_at": "2026-11-03T12:00:00.000Z",
    "updated_at": "2026-11-03T12:00:00.000Z"
  },
  "created": true,
  "refused": null
}

POST /contacts/{contact_id}/unsubscribe

Required scope: contacts:write

Unsubscribe an existing contact from the workspace. No request body. Resolve its ID using GET /contacts?email=… first. Returns 404 for an unknown ID. This is a real unsubscribe operation; it changes contact status and can emit contact.unsubscribed.

Example response · 200

Illustrative data; identifiers and timestamps vary.

{
  "data": {
    "id": "con_example",
    "email": "alex@example.com",
    "first_name": "Alex",
    "last_name": "Rivera",
    "fields": {},
    "status": "unsubscribed",
    "source": "zapier",
    "timezone": "America/New_York",
    "preferred_language": "en",
    "tags": [
      "newsletter"
    ],
    "lists": [
      {
        "id": "lst_example",
        "name": "Newsletter"
      }
    ],
    "created_at": "2026-11-03T12:00:00.000Z",
    "updated_at": "2026-11-03T12:00:00.000Z"
  }
}

POST /events

Required scope: contacts:add or contacts:write

Required: email of an existing contact and type. Optional: campaign_id of an existing workspace campaign. Type is normalized to lowercase, starts with a letter and contains up to 40 letters, digits, underscores or hyphens. Reserved names: open, click, subscribe, confirm, unsubscribe, bounce, bounced, complaint, complained, delivered, deferred, rejected, failed, sent, automation, ping. Custom event properties are not accepted by this endpoint.

Records the event and can enroll the contact in matching automations. Missing contact or campaign: 404. Repeating the same contact/type/campaign within one minute returns 200 and duplicate:true without recording another event. A new event returns 201. Daily limits reset at midnight UTC; capacity limits return 429.

Example request body

{
  "email": "alex@example.com",
  "type": "purchase"
}

Example response · 201

Illustrative data; identifiers and timestamps vary.

{
  "ok": true,
  "data": {
    "contact_id": "con_example",
    "type": "purchase",
    "campaign_id": null
  }
}

GET /campaigns

Required scope: campaigns:read

Campaign summaries. Optional status: draft, scheduled, sending, paused, sent or cancelled; invalid values return 422. Zapier uses ?status=sent for Campaign Sent samples. No pagination parameters. This endpoint does not send campaigns.

Example response · 200

Illustrative data; identifiers and timestamps vary.

{
  "data": [
    {
      "id": "cmp_example",
      "name": "November newsletter",
      "subject": "November news",
      "status": "sent",
      "list_id": "lst_example",
      "segment_id": null,
      "scheduled_at": null,
      "local_time": false,
      "started_at": "2026-11-03T12:00:00.000Z",
      "completed_at": "2026-11-03T12:01:00.000Z",
      "recipients": 10,
      "sent": 10,
      "created_at": "2026-11-02T12:00:00.000Z",
      "updated_at": "2026-11-03T12:01:00.000Z"
    }
  ]
}

POST /webhooks

Required scope: webhooks:manage; contacts:read for contact events

Create an HTTPS delivery subscription. Required url (up to 1000 characters): a public receiver URL. Private/local destinations are rejected. Optional description (200 characters) and events (array). Events: contact.subscribed, contact.confirmed, contact.unsubscribed, contact.bounced, contact.complained, campaign.sent. Omitting events or passing an empty array subscribes to all events and requires contacts:read. Maximum 10 endpoints per workspace.

Zapier creates one subscription for its selected trigger when the Zap is enabled. Save the returned id for deletion and secret for signature validation. The secret is returned only on creation. GET /webhooks and GET /webhooks/{id}, with webhooks:manage, return summaries without secrets.

Example request body

{
  "url": "https://example.com/webhooks/mbxreach",
  "events": [
    "contact.subscribed"
  ],
  "description": "Zapier"
}

Example response · 201

Illustrative data; identifiers and timestamps vary.

{
  "data": {
    "id": "whe_example",
    "url": "https://example.com/webhooks/mbxreach",
    "description": "Zapier",
    "events": [
      "contact.subscribed"
    ],
    "status": "active",
    "last_ok_at": null,
    "last_error": null,
    "created_at": "2026-11-03T12:00:00.000Z",
    "secret": "EXAMPLE_ONLY_STORE_THE_REAL_SECRET_SECURELY"
  }
}

DELETE /webhooks/{id}

Required scope: webhooks:manage

Delete the subscription identified by the returned webhook ID. No body. Zapier uses this when disabling a Zap. Returns 404 if the subscription is already absent; the integration treats that as successful cleanup.

Example response · 200

Illustrative data; identifiers and timestamps vary.

{
  "ok": true
}

Webhook delivery and signatures

MBX Reach POSTs a JSON envelope to the registered URL. Contact trigger events are contact.subscribed, contact.confirmed and contact.unsubscribed; Campaign Sent uses campaign.sent. Imports do not fire contact events. The campaign.sent event means sending finished, not that every recipient received or opened an email. ping is a test delivery, not a subscriber event.

{
  "id": "whd_example",
  "event": "contact.subscribed",
  "created_at": "2026-11-03T12:00:00.000Z",
  "workspace_id": "ws_example",
  "data": {
    "contact": {
      "id": "con_example",
      "email": "alex@example.com",
      "first_name": "Alex",
      "last_name": "Rivera",
      "status": "subscribed",
      "source": "zapier"
    },
    "lists": [
      "lst_example"
    ],
    "tags": [
      "newsletter"
    ]
  }
}

Contact deliveries contain data.contact; subscribed/confirmed events also include list/tag context when available, and unsubscribe includes data.campaign_id. Campaign deliveries contain data.campaign with id, name, subject, list_id, segment_id, sent_count and completed_at. Fields may be null when not applicable.

Headers: mbxreach-timestamp (Unix seconds) and mbxreach-signature (hex HMAC-SHA256). Compute HMAC over the exact timestamp, a dot and the unmodified raw body using the subscription secret. Reject timestamps more than 300 seconds from your clock and compare signatures in constant time. Parse JSON only after verification.

import { createHmac, timingSafeEqual } from 'node:crypto';
function verify(timestamp, signature, rawBody, secret) {
  if (!/^\d+$/.test(timestamp) ||
      Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;
  const expected = createHmac('sha256', secret)
    .update(timestamp + '.' + rawBody).digest('hex');
  const a = Buffer.from(expected);
  const b = Buffer.from(signature || '');
  return a.length === b.length && timingSafeEqual(a, b);
}

Return a 2xx promptly (receiver timeout: 10 seconds). Failures retry with backoff at 1, 5, 30, 120 and 720 minutes. Delivery can repeat; deduplicate using the envelope id. Endpoints are disabled after 25 consecutive failures. Revoking the creating API key pauses its endpoints.

Errors, limits and troubleshooting

{
  "statusCode": 403,
  "name": "insufficient_scope",
  "message": "This API key does not have the `contacts:read` scope."
}
  • 400: invalid JSON.
  • 401: missing, invalid, expired or revoked API key. Create a replacement and reconnect.
  • 403: insufficient_scope, plan_limit, ip_not_allowed, or workspace suspension/freeze. Check key scopes, plan and workspace status.
  • 404: resource or exact contact lookup not found.
  • 413: request body too large.
  • 422: invalid fields, event name, webhook destination, endpoint limit, or missing confirmation sender. Correct the request or workspace configuration.
  • 429: rate_limit_exceeded or event_limit. Back off and retry; daily event capacity resets at midnight UTC.
  • 5xx: transient server failure. Retry with exponential backoff; reconcile write results before repeating actions with side effects.

The API rate limiter is configured for 600 requests per minute per key and per workspace. Limits are enforced by the service; parallel keys do not increase workspace capacity. General JSON request bodies are limited to 2 MiB. Custom event and webhook capacity limits also apply. No general idempotency-key header is supported.

Video tutorials

Connect your app with API keys

Video in English

Related guides and support

MediaBoxEnt Technologies · mediaboxent.tech

MediaBoxEnt Technologies product news

New products when there are any, and the occasional note about the ones we already make. One click to leave.

We handle it as described in our Privacy Policy.