BusinessMCP
All docs

API reference

The BusinessMCP platform API

Everything is one workspace behind one key. Call your hosted MCP server, read the registry feed, send analytics events (including the tracker’s server-side goal and identify endpoints), and manage tasks — all with real, copyable examples.

Base URL

https://businessmcp.com

Authentication

Authorization: Bearer mcph_...

Create a Bearer mcph_* key in your dashboard Settings. The same key authenticates both the MCP endpoint and the REST tasks API. See expose your MCP endpoint for the full walkthrough.

Endpoints

GET/api/spec/toolsNone

The full tool catalogue, generated from the running server: every platform AND connector tool (with the provider that must be connected), the data classes each touches, its MCP annotations, cost class and plan gate, plus the ext_* gateway namespace, prompts, resources, the access presets and the response conventions (error envelope, pagination, units). Add ?schemas=1 for input schemas. Public and uncached-by-key, so you can review exactly what a scoped key could reach before you issue one.

Request

curl -s 'https://businessmcp.com/api/spec/tools' | jq '.counts'

Response

{
  "total": 112,
  "platform": 66,
  "connector": 46,
  "actions": 28,
  "unclassified": 0
}
GET/.well-known/oauth-protected-resourceNone

RFC 9728 protected-resource metadata, naming the authorization server. This is what Claude.ai and ChatGPT read to discover how to authorize — their connector UIs accept OAuth only and have no field for a Bearer token, so this is the path those clients take. The authorization server itself is described at /.well-known/oauth-authorization-server.

Request

curl -s 'https://businessmcp.com/.well-known/oauth-protected-resource'

Response

{
  "resource": "https://businessmcp.com/api/mcp",
  "authorization_servers": ["https://businessmcp.com"],
  "bearer_methods_supported": ["header"]
}
POST/api/mcpBearer mcph_*

Your company’s hosted MCP server. Speaks the Model Context Protocol over JSON-RPC (streamable HTTP), spec revision 2026-07-28. The bearer key resolves your workspace and its access policy. Supports tools/list + tools/call, resources/list + resources/read (businessmcp://snapshot, ://sops, ://memory, ://brain/{query}), and prompts/list + prompts/get (weekly-growth-review, funnel-audit, and more). Output is scrubbed by the key’s policy. An unauthenticated request returns 401 with a WWW-Authenticate challenge pointing at /.well-known/oauth-protected-resource, so a spec-compliant client can discover the OAuth flow on its own.

Request

curl -X POST 'https://businessmcp.com/api/mcp' \
  -H 'Authorization: Bearer mcph_your_key_here' \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
      "name": "get_analytics",
      "arguments": { "window_days": 30 }
    }
  }'

Response

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\"visitors\":1284,\"sessions\":1610,\"leads\":41,\"conversions\":18,\"revenue\":2489,\"units\":{\"visitors\":\"distinct people in the window\",\"revenue\":\"USD\"},\"window\":{\"since\":\"2026-08-05\",\"end\":\"2026-09-03\"},\"compare\":{\"change_pct\":{\"visitors\":12}}}"conversion_rate\":0.031,\"revenue_cents\":248900,\"range\":\"30d\"}"
      }
    ]
  }
}
GET/api/registry/v0/serversPublic

MCP Registry v0-shaped feed of servers in this registry, so the marketplace is machine-consumable by MCP clients and aggregators. Supports search, limit (max 200) and offset. Every entry advertises the streamable-HTTP remote.

Request

curl -X GET 'https://businessmcp.com/api/registry/v0/servers?search=stripe&limit=2&offset=0'

Response

{
  "servers": [
    {
      "name": "com.mcphosting/stripe",
      "description": "Customers, payments and subscriptions",
      "version": "1.0.0",
      "status": "active",
      "_meta": {
        "com.mcphosting": {
          "vendor": "Stripe",
          "featured": true,
          "listing_url": "https://businessmcp.com/mcp-servers/stripe"
        }
      },
      "remotes": [
        { "type": "streamable-http", "url": "https://businessmcp.com/api/mcp" }
      ]
    }
  ],
  "metadata": { "count": 1, "total": 1, "next_offset": null }
}
POST/api/analytics/eventPublic (origin-verified)

The analytics beacon that track.js posts to. Cookieless and cross-origin; verifies the origin against your registered domain and responds fast so it never blocks page unload. You rarely call this by hand — the tracking script does — but the shape is public. type is pageview | goal | revenue | identify | engagement | exposure: a pageview carries pg {t, w, h, l} (title, viewport, language); an engagement beacon carries engaged_ms and scroll_pct for the page being LEFT and never counts as a pageview; identify is routed to the CRM, not the firehose. eid is the client event id (also emitted as the mcph:goal DOM event) that dedupes a server-forwarded conversion against your own pixel, v is the tracker build, and att is the first (ft) and last (lt) touch the tracker kept client-side. The body is JSON, but the Content-Type is text/plain: a browser must never send application/json here — a non-safelisted type forces a CORS preflight that a credentialed sendBeacon fails, and the beacon is silently dropped. The server JSON-parses the raw body regardless of Content-Type.

Request

# Content-Type stays text/plain (browsers must not send application/json — CORS preflight)
curl -X POST 'https://businessmcp.com/api/analytics/event' \
  -H 'Content-Type: text/plain' \
  -d '{
    "trackerId": "YOUR_TRACKER_ID",
    "type": "revenue",
    "name": "purchase",
    "eid": "e1m9x2k4abcd",
    "v": "2.0.0",
    "url": "https://yoursite.com/checkout/success",
    "referrer": "https://google.com",
    "revenueCents": 4900,
    "visitorId": "v8f3a2c1b",
    "metadata": { "plan": "pro" },
    "att": {
      "ft": { "src": "google", "med": "cpc", "cmp": "brand", "ids": { "gclid": "Cj0K…" }, "lp": "/pricing", "ts": 1756400000000 },
      "lt": { "ref": "https://news.ycombinator.com/", "lp": "/", "ts": 1756486400000 }
    }
  }'

Response

HTTP/1.1 204 No Content
POST/api/tracker/{trackerId}/goalPublic (tracker id in the path)

Trusted server-side goal — record a conversion from your own backend (a payment webhook, a job) that the browser tracker cannot see. Body: name (required), visitorId (the id from mcph.getVisitorId(), to stitch the journey), revenue_cents (number → a revenue event) and metadata (object). No user-agent bot detection runs on this path, so keep it server-to-server. Origin is checked only when a browser sends one; a server call without an Origin passes. Limits: 120/min per IP and 1,200/min per tracker; bodies over 4KB get 413. Errors: 400 missing name or invalid body, 403 foreign browser origin, 404 unknown tracker, 429 over limit. The sibling POST /api/tracker/{trackerId}/bot-hit records JS-less AI crawlers — see /guides/track-ai-crawlers-server-side.

Request

curl -X POST 'https://businessmcp.com/api/tracker/YOUR_TRACKER_ID/goal' \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "purchase",
    "visitorId": "v8f3a2c1b",
    "revenue_cents": 4900,
    "metadata": { "plan": "pro", "order_id": "ord_1192" }
  }'

Response

{ "ok": true }
POST/api/tracker/{trackerId}/identifyPublic (tracker id in the path)

Server-side identify for funnels the client tracker cannot see inside — a cross-origin iframe (Typeform, Heyflow) or an off-domain funnel — pointed at from the provider’s webhook or your backend. Body: email (required, must be a real address — disposable and placeholder domains answer 400), visitorId (optional, for a full journey stitch when the funnel was entered with a decorated id) and traits (object; traits.source defaults to "webhook"). A business email auto-derives the company for enrichment. A workspace’s own teammates are never minted as leads: their address returns { ok: true, skipped: "teammate" }. Same limits and error codes as the goal endpoint.

Request

curl -X POST 'https://businessmcp.com/api/tracker/YOUR_TRACKER_ID/identify' \
  -H 'Content-Type: application/json' \
  -d '{
    "email": "jordan@acme.com",
    "visitorId": "v8f3a2c1b",
    "traits": { "source": "typeform", "plan": "pro" }
  }'

Response

{ "ok": true }
GET/api/tracker/{trackerId}/configPublic (tracker id in the path)

The public per-site config track.js fetches on load to decide whether to show the support widget and whether to load the in-house heatmap/replay recorder (or a legacy Clarity/Hotjar id). Always answers 200: an unknown or disabled tracker returns { enabled: false } so the loader cleanly no-ops. Pass ?lang= (the browser language) to receive the widget strings translated when auto-translate is on; the IP country is the fallback. Cached 60s. Nothing here is secret — it is what every visitor’s browser already downloads.

Request

curl -X GET 'https://businessmcp.com/api/tracker/YOUR_TRACKER_ID/config?lang=de'

Response

{
  "enabled": true,
  "config": {
    "mode": "chat",
    "position": "right",
    "brandColor": "#2a75ba",
    "headerTitle": "Chat with us",
    "greeting": "Hi! How can we help?",
    "showPhone": false,
    "autoTranslate": true
  },
  "lang": "de",
  "strings": { "header_title": "…", "greeting": "…" },
  "heatmaps": {
    "mode": "inhouse",
    "sample_rate": 1,
    "replay_enabled": true,
    "mask_all_inputs": true,
    "clarity_id": null,
    "hotjar_id": null,
    "exclude_paths": ["/u/*", "/book/*"]
  }
}
POST/api/v1/runsBearer mcph_*

Drive a full Assistant run from code. POST a prompt (optionally with a thread_id to continue a conversation, and mode "deep" for a bigger, verified analysis pass) to queue a run; it returns the thread_id. This is the same Head-of-BI agent, tools and access policy as the dashboard Assistant.

Request

curl -X POST 'https://businessmcp.com/api/v1/runs' \
  -H 'Authorization: Bearer mcph_your_key_here' \
  -H 'Content-Type: application/json' \
  -d '{
    "prompt": "How did conversions trend this month, and which channel drove them?",
    "mode": "deep"
  }'

Response

{ "thread_id": "th_7b21" }
GET/api/v1/runsBearer mcph_*

Poll a run by thread_id. Returns the messages and the latest assistant reply once the run finishes. Poll every second or two until the reply is present.

Request

curl -X GET 'https://businessmcp.com/api/v1/runs?thread_id=th_7b21' \
  -H 'Authorization: Bearer mcph_your_key_here'

Response

{
  "thread_id": "th_7b21",
  "status": "done",
  "reply": "Conversions rose 18% MoM to 312, led by Organic Search (…).",
  "messages": [ /* full turn history */ ]
}
GET/api/v1/meBearer mcph_*

Return the calling key’s scope: whether it has full access, its label, whether it can take actions, and the data classes it is permitted. Use it to verify a scoped key before relying on it.

Request

curl -X GET 'https://businessmcp.com/api/v1/me' \
  -H 'Authorization: Bearer mcph_your_key_here'

Response

{
  "full_access": false,
  "label": "Analytics-only",
  "can_act": false,
  "data_classes": ["behavior"]
}
GET/api/v1/tasksBearer mcph_*

Public REST surface for the CLI and external automations. GET lists tasks (filter by status and dept); POST creates a task. Authenticated with the same mcph_* key as your MCP endpoint.

Request

curl -X GET 'https://businessmcp.com/api/v1/tasks?status=todo' \
  -H 'Authorization: Bearer mcph_your_key_here'

Response

{
  "tasks": [
    {
      "id": "t_9c21",
      "title": "Draft Q3 SEO plan",
      "dept": "Marketing",
      "status": "todo"
    }
  ]
}
POST/api/v1/tasksBearer mcph_*

Create a task and optionally seed it with a first message that kicks off an agent run. Requires a title; dept defaults to Operations.

Request

curl -X POST 'https://businessmcp.com/api/v1/tasks' \
  -H 'Authorization: Bearer mcph_your_key_here' \
  -H 'Content-Type: application/json' \
  -d '{
    "title": "Summarize last week\u2019s revenue by channel",
    "dept": "Marketing",
    "message": "Pull the numbers and post a short summary."
  }'

Response

{
  "id": "t_9c34",
  "title": "Summarize last week\u2019s revenue by channel",
  "status": "todo"
}
GET/api/v1/exportBearer mcph_*

Programmatic CSV export. type=analytics (the multi-section overview report), type=sessions (one row per session) or type=contacts (the CRM). Analytics/sessions require the behavior data class, contacts require contact_pii; revenue columns are blank without the revenue class. Max 30 exports/hour.

Request

curl -X GET 'https://businessmcp.com/api/v1/export?type=sessions&range=30' \
  -H 'Authorization: Bearer mcph_your_key_here' -o sessions.csv

Response

day,channel,referrer,country,device,...
"2026-08-27","Organic Search","google.com","US","desktop",...
POST/api/v1/trackBearer mcph_*

Record product-usage events from your own backend (Segment-style). Send {events:[…]} (up to 500), a single event object, or application/x-ndjson with one event per line; 1MB max. Each event takes a name (normalized to lowercase [a-z0-9_:-], max 64 — the normalized name is echoed back), an optional user_id (your product user id, resolved to the CRM contact and their web journey), visitor_id (the mcph_vid from the tracker), an ISO 8601 timestamp up to 30 days in the past (defaults to now; more than 5 minutes ahead is rejected), properties, and revenue_cents for purchases. Bad events are reported per index and never fail the batch. Events count toward your monthly events allowance; they are never dropped for quota.

Request

curl -X POST 'https://businessmcp.com/api/v1/track' \
  -H 'Authorization: Bearer mcph_your_key_here' \
  -H 'Content-Type: application/json' \
  -d '{
    "events": [
      { "name": "Invite Sent", "user_id": "u_42", "timestamp": "2026-09-05T10:12:00Z",
        "properties": { "plan": "pro", "seats": 3 } },
      { "name": "Order Placed", "user_id": "u_42", "revenue_cents": 4900 },
      { "name": "identify" }
    ]
  }'

Response

{
  "ok": true,
  "accepted": 2,
  "rejected": [ { "index": 2, "error": "`name` is reserved or normalizes to empty (use [a-z0-9_:-])" } ],
  "events": [
    { "index": 0, "name": "invite_sent", "timestamp": "2026-09-05T10:12:00.000Z", "day": "2026-09-05" },
    { "index": 1, "name": "order_placed", "timestamp": "2026-09-06T08:00:01.120Z", "day": "2026-09-06" }
  ]
}
POST/api/v1/identifyBearer mcph_* (crm_actions)

Link your product user id to a CRM contact and merge traits. Pass user_id and/or email (one is required) plus an optional visitor_id to stitch the web journey; traits are merged, never clobbered. Disposable email domains are refused and your own workspace members are skipped. The calling server IP is never treated as the visitor.

Request

curl -X POST 'https://businessmcp.com/api/v1/identify' \
  -H 'Authorization: Bearer mcph_your_key_here' \
  -H 'Content-Type: application/json' \
  -d '{ "user_id": "u_42", "email": "ana@acme.com", "traits": { "name": "Ana Silva", "plan": "pro" } }'

Response

{ "ok": true, "contact_id": "c_7f31…", "created": false }
POST/api/v1/groupBearer mcph_* (crm_actions)

Upsert an account (your own company/tenant object) keyed by account_id, and attach the contacts behind user_ids to it. Only the fields you send are updated. Accounts are private to your workspace; plan, mrr_cents and seats are yours to define.

Request

curl -X POST 'https://businessmcp.com/api/v1/group' \
  -H 'Authorization: Bearer mcph_your_key_here' \
  -H 'Content-Type: application/json' \
  -d '{ "account_id": "acct_acme", "name": "Acme", "domain": "acme.com",
        "plan": "pro", "mrr_cents": 49900, "seats": 12, "user_ids": ["u_42", "u_43"] }'

Response

{ "ok": true, "account_id": "acct_acme", "id": "a_10c2…", "created": true, "linked": 2, "unknown_user_ids": [] }
GET/api/v1/contactsBearer mcph_* (contact_pii)

The CRM as JSON — one record per contact with scores, subscription facts, account, company profile, identities and traits. Keyset-paginated on (updated_at, id): pass next_cursor back as ?cursor= until has_more is false, and ?updated_since=<iso> to pull only what changed (the incremental-sync loop). Filters: ?stage=<lifecycle|qualified>, ?account_id=<uuid>, ?email=. Visitor ids are counted, not listed, unless ?include=identities. Money fields are null without the revenue class; company_profile is redacted without enrichment. Max 200 per page, 600 requests/min per key. GET /api/v1/contacts/{id} returns one record with every identity listed.

Request

curl -s 'https://businessmcp.com/api/v1/contacts?updated_since=2026-09-01T00:00:00Z&limit=100' \
  -H 'Authorization: Bearer mcph_your_key_here'

Response

{
  "items": [
    { "id": "c_7f31…", "email": "ana@acme.com", "name": "Ana Silva", "lifecycle_stage": "sql",
      "first_channel": "Organic Search", "deal_value_usd": 12000, "ltv_usd": 499,
      "subscription": { "status": "active", "plan": "pro", "mrr_usd": 499, "seats": 12, "trial_ends_at": null, "churned_at": null },
      "scores": { "lead": 82, "band": "hot", "fit": 70, "intent": 90, "churn_risk": 10, "is_pql": true, "ltv_estimate_usd": 2500 },
      "account": { "id": "a_10c2…", "external_id": "acct_acme", "name": "Acme" },
      "company_profile": { "domain": "acme.com", "name": "Acme Inc", "industry": "computer software", "employee_range": "51-200" },
      "identities": [ { "kind": "email", "value": "ana@acme.com" }, { "kind": "user_id", "value": "u_42" } ],
      "visitor_id_count": 2, "traits": { "plan": "pro" },
      "created_at": "2026-08-30T09:12:44.120331+00:00", "updated_at": "2026-09-05T18:02:10.551902+00:00" }
  ],
  "has_more": true,
  "next_cursor": "eyJ1IjoiMjAyNi0wOS0wNVQxODowMjoxMC41NTE5MDIrMDA6MDAiLCJpIjoiYzdmMzEifQ"
}
GET/api/v1/accountsBearer mcph_* (contact_pii)

Your accounts (the objects POST /api/v1/group writes) as JSON, keyset-paginated on (updated_at, id) with the same ?updated_since / ?cursor / ?limit contract as contacts. Filters: ?external_id=, ?domain=. mrr_usd is null without the revenue class.

Request

curl -s 'https://businessmcp.com/api/v1/accounts?updated_since=2026-09-01' \
  -H 'Authorization: Bearer mcph_your_key_here'

Response

{
  "items": [
    { "id": "a_10c2…", "external_id": "acct_acme", "name": "Acme", "domain": "acme.com",
      "plan": "pro", "mrr_usd": 499, "seats": 12, "traits": {},
      "company_profile": { "domain": "acme.com", "name": "Acme Inc", "industry": "computer software", "employee_range": "51-200" },
      "created_at": "…", "updated_at": "…" }
  ],
  "has_more": false,
  "next_cursor": null
}
GET/api/v1/eventsBearer mcph_* (behavior)

Sessions as JSON — one row per visit from the durable session store, the same rows the CSV export serves: day, visitor and contact ids, channel and UTMs, entry/exit pages, pageviews, engaged seconds, goals, revenue, geography, language and device. Window ≤90 days: ?from=YYYY-MM-DD&to=YYYY-MM-DD, or ?updated_since=<day>, default the trailing 30 days; keyset-paginated on (day, id) via ?cursor=; ?visitor_id= narrows to one person. revenue_usd is null without the revenue class; answers 402 while the workspace is traffic-locked.

Request

curl -s 'https://businessmcp.com/api/v1/events?from=2026-09-01&to=2026-09-06&limit=200' \
  -H 'Authorization: Bearer mcph_your_key_here'

Response

{
  "items": [
    { "id": "s_…", "day": "2026-09-05", "visitor_id": "v_…", "contact_id": "c_7f31…",
      "channel": "Organic Search", "referrer": "google.com", "entry_path": "/pricing", "exit_path": "/signup",
      "pageviews": 4, "duration_seconds": 212, "bounce": false, "converted": true, "goal_names": ["signup"], "revenue_usd": 0,
      "utm_source": null, "utm_medium": null, "utm_campaign": null, "utm_content": null, "utm_term": null,
      "country_code": "US", "region": "CA", "city": "San Francisco", "language": "en", "device_type": "desktop", "os": "macOS", "browser": "Chrome" }
  ],
  "has_more": false,
  "next_cursor": null,
  "window": { "from": "2026-09-01", "to": "2026-09-06" }
}

Events API

/api/v1/track, /api/v1/identify and /api/v1/group feed product-usage data from your own backend into the same analytics, CRM and identity graph the tracking script writes to. The public tracker webhooks (/api/tracker/<id>/goal|identify) stay the no-auth surface for Zapier-style tools; the Events API needs a Bearer key because a user_id merge is a CRM write.

Identity and sessions

  • user_id is your product user id. It resolves to the contact (and through it to the visitor the tracker saw), so server events inherit the person’s first-touch channel and UTMs.
  • Server events form one session per user (or visitor, or anonymous) per UTC day — they never add page views, bounces or unique visitors.
  • Event names are normalized exactly like mcph.track(); identify, pageview and ping are reserved.
  • revenue_cents records a purchase (and forwards to your ad platforms when server-side conversions are enabled).

Timestamps, batching and limits

  • timestamp is ISO 8601 (or an epoch number). Up to 30 days in the past is accepted for backfills; a past day is re-processed by the next analytics run (within ~2 hours). More than 5 minutes ahead is rejected.
  • Up to 500 events per request, 1MB per body; NDJSON is accepted for streaming writers.
  • Per-event errors come back as rejected[{index, error}]; the rest of the batch is stored. A 400 means nothing was stored.
  • 600 requests/minute per key and 3,000/minute per workspace; a 429 carries Retry-After.
  • identify and group require the crm_actions data class on the key’s access policy.

Read API

GET /api/v1/contacts, /api/v1/contacts/{id}, /api/v1/accounts and /api/v1/events are the pull-sync surface for a warehouse, a reverse-ETL tool or your own backend — the same records the data-change webhooks push, so one parser serves both. Where the CSV export is a snapshot, these are incremental.

Pagination and sync

  • Every list answers {items, has_more, next_cursor}. Pass next_cursor back as ?cursor= until has_more is false.
  • Cursors are keyset (the last row seen), never offsets — stable while rows are being written. Treat them as opaque.
  • ?updated_since= (ISO day or instant) is the incremental loop: store the newest updated_at you saw and ask for everything after it.
  • ?limit= up to 200 (default 50). Events span at most 90 days per call.
  • Money is *_usd decimals; null means unknown, never zero.

Access policy

  • Contacts and accounts need the contact_pii class; events need behavior. A key without it answers 403.
  • Without revenue: deal value, LTV, MRR, LTV estimate and session revenue come back null.
  • Without enrichment: the company_profile block reads [redacted].
  • 600 requests/min per key, 3,000/min per workspace → 429 with Retry-After.
  • Mint a scoped key under Settings → API & Vault; the presets there decide which classes it carries.

Outgoing webhooks

Subscribe an HTTPS endpoint in Settings → API & Vault and we POST workspace events to it: lead.created, visitor.hot, outreach.replied, meeting.booked and alert.raised — plus the data-change events contact.created, contact.updated, contact.scored, company.identified, account.updated and revenue.recorded (opt-in per endpoint). Payloads can contain lead names and email addresses — point them only at systems you trust. data.url is always absolute; data.entity is the contact (or a bare {type, id} for a company/visitor) the event is about, with every field present and null when unknown.

Payload

POST <your URL>
X-MCPH-Event: lead.created
X-MCPH-Timestamp: 1756400000
X-MCPH-Signature: sha256=<hex>

{
  "id": "…", "event": "lead.created",
  "created_at": "2026-08-28T12:00:00.000Z",
  "workspace_id": "…",
  "data": {
    "kind": "lead", "title": "New lead: …", "body": "…",
    "url": "https://businessmcp.com/dashboard/crm/contact/…",
    "entity": {
      "type": "contact", "id": "…",
      "name": "…", "email": "…", "company": "…", "domain": "…",
      "stage": "lead", "lead_score": 82, "band": "hot",
      "lead_quality_band": "ok", "deal_value_cents": null,
      "owner_name": null, "first_channel": "Organic Search",
      "hdyhau": null, "last_page": "/pricing",
      "dashboard_url": "https://businessmcp.com/dashboard/crm/contact/…"
    }
  }
}

Verify & semantics

// Node — reject if it doesn't match
const expected = 'sha256=' + crypto
  .createHmac('sha256', WEBHOOK_SECRET)
  .update(timestamp + '.' + rawBody)
  .digest('hex')
  • 5s timeout, one retry ~2s later.
  • Auto-disabled after 20 consecutive failures (re-enable in Settings).
  • Up to 5 endpoints per workspace; ~300 deliveries/endpoint/hour.
  • Answer 2xx quickly — do the work async on your side.

Data events

The free reverse-ETL path. data is the SAME record GET /api/v1/contacts (or /accounts) returns, so a consumer that already parses the read API parses these for free. Every delivery carries its own id — dedupe on it, and reconcile anything the hourly cap dropped with ?updated_since=.

X-MCPH-Event: contact.scored

{
  "id": "…", "event": "contact.scored",
  "ts": "2026-09-06T02:14:09.000Z",
  "workspace_id": "…",
  "data": { …the /api/v1/contacts record… }
}

// company.identified
"data": {
  "visitor_id": "v_…", "contact_id": "c_…",
  "via": "ip", "sources": ["registry"], "confidence": 0.9,
  "company": { "id": "…", "domain": "acme.com", "name": "Acme",
               "industry": "computer software", "employee_range": "51-200" },
  "contact": { …the contact record… }
}
  • contact.created / contact.updated — every identify, form, signup, import, merge and Stripe stitch; the record after the write.
  • contact.scored — once per contact per nightly run, only when the lead score, band, PQL flag or churn risk actually changed.
  • company.identified — an identified person was linked to a company; anonymous reveals do not fire.
  • account.updated — the account record plus created: true|false.
  • revenue.recorded{amount_usd, currency, source, visitor_id, contact_id, channel, utm_*, contact} for every server-verified purchase.
  • Pick the events per endpoint in Settings → API & Vault; the notification events stay selected by default, data events are opt-in.

Errors & limits

Status codes

  • 401 — missing or invalid mcph_* key
  • 400 — malformed request (e.g. a task with no title)
  • 413 — analytics beacon body over the 8KB cap
  • 204 — analytics event accepted

Quotas

Metered actions are gated by your plan's monthly analytics-event quota, and agent runs are protected by a per-run cost ceiling. When you hit a limit you get a clear message — upgrade your plan or add an event top-up to raise it.

Prefer the terminal?

The mcphosting CLI wraps these APIs for one-command connect, search and list.

CLI docs