Authentication and API keys
API keys for your systems, OAuth 2.1 with PKCE for apps that act on behalf of a person, and how to target an Account.
Every request carries Authorization: Bearer <credential>. There are two kinds of credential and both act as a person of the organization: they can never do more than that person.
| Credential | Prefix | What for |
|---|---|---|
| API key | mbs_live_… | Your servers, scripts, Make, Zapier or Cursor. An owner or admin creates it and it represents a member. |
| OAuth token | mbo_… (and mbr_… to refresh it) | An app that acts on behalf of the person who authorizes it: Claude, ChatGPT or your integration. |
API keys
- Where: Settings → API & MCP → API keys (owners and admins). The API key is shown only once; the list keeps its prefix so you can recognize it.
- Who it represents: a member of the organization (you, by default). Nobody can create an API key on behalf of someone with a higher role than theirs.
- Permissions: per module, Read or Read and write; Allow deletions is separate and off by default. See Permissions and scopes.
- Expiry: never, or 30, 90, 365 days.
- Rotate creates a new API key with the same permissions; the previous one keeps working for 24 hours so you can switch the integration without a gap.
- Suspend, reactivate and revoke from the same tab. If the person it represents leaves the organization, the API key is suspended automatically.
- Limit: the number of active API keys depends on the plan (see Limits).
Test API keys: the practice organization
A test API key (mbs_test_ and 46 characters) works only on your workspace's practice organization: a separate organization with example CRM data (a pipeline with its stages, companies, persons, deals in different stages and activities) and a few activity labels to try labeling entries (a practice organization created before 2026-10-12 gets them when reset with platform.sandbox.reset), created on its own with the first test API key. Use it to build and try an integration without touching real data.
- How to create one: in Settings → API & MCP → API keys, Create API key with the Test environment (workspaces only). It acts as an owner or admin of the workspace and does not reach Accounts:
MB-Organizationandorg_idchange nothing, the request always goes to the practice organization. - Nothing leaves MB Suite: sending an email (
crm.email.send) or a WhatsApp message (crm.whatsapp.send_text,send_template,send_media) is simulated: the copy stays in the practice organization (the email's activity, the message in its conversation) and the response carriessimulated: true(in the API, also theMB-Simulated: trueheader). Exporting a PDF is simulated too: nothing is rendered and a one-page PDF that says so is stored (dashboards.dashboards.export_pdfreturns the link;reports.instances.export_pdfanswersqueuedand the link comes fromreports.instances.pdf_url). Anything else that reaches another platform answers409 sandbox_external_unavailable.GET /v1/meandmb_contextlist inpracticewhat is simulated. - Its own quota: the practice organization has its own limits, the Practice row of Limits, and never uses your workspace's quota.
- Test webhooks: in the Webhooks tab, the Test environment creates subscriptions in the practice organization. They receive only what test API keys do and are delivered for real, signed the same way; no live subscription receives events from the practice organization.
- Reset test data: from the Practice organization card of the API keys tab, or with
POST /v1/sandbox/reset(first withdry_run). It deletes everything the practice organization accumulated and seeds the example data again; API keys and test subscriptions are kept.GET /v1/sandboxreturns its state.
curl https://api.mb-suite.com/v1/crm/deals?limit=5 \
-H "Authorization: Bearer $MB_TEST_KEY"Workspaces and Accounts: MB-Organization
A workspace API key can work on its Accounts if you chose Also access Accounts when you created it (all or some, and always only the ones the person can see). To point a request at an Account, send its id in the MB-Organization header (or org_id in the query or the body). Without it, the request goes to the API key's organization.
curl https://api.mb-suite.com/v1/crm/deals \
-H "Authorization: Bearer $MB_API_KEY" \
-H "MB-Organization: ACCOUNT_ID"An organization the API key cannot reach answers 404 organization_not_found: we never say whether it exists. An Account can also have its own API keys if it pays its own subscription or if its agency allows it (Partners → View permissions).
OAuth 2.1 for apps
An app that acts on behalf of a person uses the authorization_code flow with PKCE (S256, required). Clients are public, without a secret. Everything you need is in the discovery document: https://mb-suite.com/.well-known/oauth-authorization-server.
- Registration: dynamic (RFC 7591) at
https://mb-suite.com/api/platform/oauth/register, withhttps://or loopback-with-portredirect_uris; or aclient_idthat is the URL of your metadata document. - Authorize: send the person to
https://mb-suite.com/oauth/authorizewithresource(required, RFC 8707):https://api.mb-suite.comfor the API orhttps://mcp.mb-suite.comfor the MCP. A token only works for the resource you requested. - Consent: the person picks the organization and reviews the permissions (writing and deleting are off), the Accounts and, if the organization allows it, connected mailboxes and calendars.
- Tokens: the access token (
mbo_…) lasts 1 hour; the refresh token (mbr_…), 30 days, and it is single use: every refresh returns a new one and reusing an old one revokes the whole authorization. No authorization lasts longer than 365 days. - Revoke:
https://mb-suite.com/api/platform/oauth/revoke(RFC 7009), or the person from Settings → API & MCP → MCP, with “Disconnect”. It cuts access instantly.
GET https://mb-suite.com/oauth/authorize
?response_type=code
&client_id=CLIENT_ID
&redirect_uri=https://app.example.com/callback
&code_challenge=CODE_CHALLENGE
&code_challenge_method=S256
&resource=https%3A%2F%2Fapi.mb-suite.com
&scope=crm:write
&state=RANDOM_STATEScopes
scope uses the same permissions as API keys: <module>:read, <module>:write and <module>:delete (the deletion switch), plus connected_content:read for connected mailboxes and calendars. Without scope, the app asks only for read access to the modules with operations. Writing is always the person's call in the consent screen, even when the app requests it with scope: “Read and write” is a switch that is off by default (with scope, it only covers the modules where the app asked for :write), and Members, Branding, Accounts, Client Portal, Email Marketing and Integrations each have their own. Deleting is separate, and developer_platform (creating webhooks) must be requested explicitly. Ask only for what your integration uses: whatever the person leaves off is not in the token, and the scope in the /token response lists what was granted.
invalid_request, invalid_grant, invalid_client…), not with the API's error format.