Skip to main content
Every client connects to the same address (POST https://dash.dmly.io/mcp) and there are two ways to authenticate to it: sign in with OAuth, or paste a dmly_ API key. That choice also decides what the connection is allowed to do, so read MCP overview first if you have not.

Which method

Neither is a fallback for the other. If your client offers a connector or sign-in flow, OAuth is the shorter path: nothing to mint, and no key to paste into a file. If it wants a URL and a header, use a key.

Connect over OAuth

DMLY publishes everything a client needs to register itself: the discovery documents at /.well-known/oauth-authorization-server and /.well-known/oauth-protected-resource, dynamic client registration at /oauth/register, and the authorization-code flow with PKCE (S256) plus refresh tokens. Nothing has to be created in DMLY first; you give the client the address and it does the rest. There is one scope, mcp:use, and the client is issued it automatically; it is not something you pick, and it decides nothing on its own.
Callback addresses. A client that registers an https:// callback always works; that is what claude.ai and other hosted clients use. A native desktop client that registers a private-use scheme instead (RFC 8252) is accepted for claude://, cursor:// and vscode://; any other scheme is refused at registration with 400 invalid_redirect_uri, before you ever see a consent screen.
Registration is rate limited. /oauth/register allows 10 registrations an hour from one address, inside a 200-an-hour ceiling across the platform that a caller rotating addresses cannot sidestep. Past either, it answers 429 with {"error":"too_many_requests"} and Registration is rate limited. A real client registers once per install, so this only bites a script in a loop: register once and keep the client_id you were issued rather than re-registering per connection.
1

Give the client the server address

https://dash.dmly.io/mcp is the same address for every workspace, including one whose people reach DMLY on an agency’s own domain. In a hosted client this is the “custom connector” or “remote MCP server” field; wording differs by client and changes with releases, so follow its own documentation for where that lives.
2

Sign in to DMLY

The client opens a DMLY page. If you are not signed in already you get the login screen first, then come back to the same place.
3

Choose the workspace

The consent screen names the client that is asking, says what it will be able to do, and gives you exactly one choice: which Workspace it may work in. Pick it and select Connect (or Cancel to refuse). It will only ever see that one workspace.Above the picker, whenever the authorization request carries a redirect_uri, the screen also shows the host that code would be sent to: Access will be sent to host, followed by “If you don’t recognise that address, close this page instead of connecting.” Read that line rather than the name at the top. Registration is open by design, so the client name is whatever the registering party typed and proves nothing; the callback host is the one element of the screen a phisher cannot fake, which is why it is there.
4

Ask it something read-only

Before you let it act: How many conversations are open? See Verify it works.

What an OAuth connection may do

There is no scope to pick on the consent screen, because the one scope a connection carries (mcp:use) authorises nothing by itself. Your team role is the whole gate: the connection acts as you, and gets the tools your role would let you use in the dashboard. The per-tool permissions are listed in the tools reference, and they are the same keys you tick when building a role. See Roles and permissions. Per-member restrictions count too: a member whose inbox access was switched off gets no inbox or messaging tools here either, and one whose reporting access was switched off gets no get_workspace_overview, which is the first call the server tells every client to make. A connection missing it answers not found on that call and carries on with the rest.
A tool your role does not grant is not registered, so it never appears in tools/list, and calling it by name anyway answers not found, because the server resolves a call through the same eligibility check as the listing. Change someone’s role and their connection changes with it; nothing has to be reconnected.
Contact details are masked here too. If the connecting person’s role withholds contacts.view_phone or contacts.view_email, phone numbers and email addresses come back masked over MCP exactly as they do in the dashboard, so a connection really is bounded by what its owner can see, PII included. The same holds on an API key, where the acting member is the one who generated it. Only the REST API is unmasked.
Want an AI connection that can read but never act, over OAuth? Connect it as someone whose role is Viewer. Otherwise mint an AI: read key, below.
The workspace you picked is remembered against that (person, client) pairing. Re-consenting with a different workspace moves the connection rather than adding a second one, and two different AI clients can hold two different workspaces, which is how you work across both. Membership is re-checked on every request, so someone removed from a workspace stops reaching it immediately, without anyone having to tidy up the connection. How long a connection lasts. An access token is good for 15 days and the refresh token behind it for 90. A client in normal use refreshes silently and you never notice. One left untouched for more than 90 days has nothing left to refresh with, and reconnecting means going through sign-in and consent again.

Connect with an API key

1. Mint a key with MCP access

MCP access is opt-in per key. A key with no scopes reaches the REST API and nothing else, which is where every key minted before the MCP server existed still sits.
1

Open the API keys page

Go to Configurations → Integrations and select the REST API tile, then Generate key.
2

Name it after the client

Use the name of the thing that will hold it: “Claude Desktop”, “support agent”, “Cursor”. The name is how you will recognise it when you revoke it.
3

Choose what this key can reach

The picker is labelled What this key can reach and has exactly three options:REST API only is the default and the right answer for Zapier, n8n and your own integrations. Pick one of the AI options only for a client that will speak MCP.
4

Copy the key now

It is shown once. DMLY stores a SHA-256 hash, so it cannot be retrieved or re-shown later. The plaintext starts with dmly_.
5

Copy the endpoint

Once the workspace has at least one AI-scoped key, the same page shows a Connect an AI assistant panel with the address ending in /mcp. That is what the client connects to.

Read or read-and-act

This is the choice that matters on a key, and it is enforced by not existing rather than by refusing.
  • mcp:read registers up to 16 tools. The 21 write tools are not in tools/list at all, so the model cannot call one, cannot hallucinate having called one, and cannot be talked into it. Nothing it does reaches a customer.
  • mcp:write registers up to all 37, and implies read. It can message customers, take and cancel bookings, raise and send invoices, record payments and switch automations on or off, inside the safety model.
The scope is a ceiling rather than the whole story. A key generated in DMLY records the member who generated it, and over MCP it resolves that member’s dashboard permissions as well, so it can never reach a tool they could not use themselves. Only an owner, an admin, or a custom role holding Manage integrations & API keys can generate one; owners and admins hold every permission, so for their keys the scope really is the only limit, while a key generated by a custom role is bounded by that role too. Three things follow, and they are not equivalent.
  • Narrowing that member’s role narrows the key, on its next call. The role is read live on every request, so nothing has to be reissued.
  • Removing them from the workspace stops the key at /mcp entirely. Every request answers 403 with a message saying the key’s creator is no longer a member, and asking an admin for a new key.
  • Revoking the key is the only lever that stops it everywhere. The first two leave the key’s REST API access untouched: /api/v1 neither resolves a member nor refuses a departed one, so the key keeps working there whatever happens to the person.
(A key with no member recorded against it, which the dashboard never generates, is governed by its scope alone.)
Start read-only. You can mint a read-and-act key later once you have watched the client work with your real data.
A key’s access level cannot be edited after it is created. To change it, generate a replacement at the level you want, move the client onto it, then Revoke the old one. Revocation takes effect immediately.

2. Give the client the address and the key

The server speaks Streamable HTTP: JSON-RPC 2.0 over POST to /mcp. The key goes in a header. Either works, and they are the same two the REST API accepts:
If both are present, x-api-key wins. Most desktop and editor clients are configured from a JSON file that maps a server name to a URL and headers. The shape below is the common one for a remote MCP server:
Client config schemas differ, and they change. The key names above are the widely used shape, not a guarantee for any particular product. Check your client’s own documentation for where the file lives and what it calls the URL and header fields, then fill it in with the endpoint and key from step 1. The file holds a plaintext credential to your workspace, so treat it like any other secrets file: never commit it.
Some clients ask for a remote MCP server URL in their own UI rather than a file. Same two values: the /mcp address, and the key as a header.

Verify it works

The cheapest check is that the endpoint is reachable and enforcing auth. With no credential it must answer 401:
Add the credential and the same call returns the tool list. Count what comes back: on a key generated by an owner or an admin it is 16 tools on mcp:read, 37 on mcp:write, which is the fastest way to confirm the key carries the scope you meant it to. A key generated by a custom role returns fewer, because that role’s permissions apply as well. Over OAuth the count is whatever your role grants: 37 for an admin, 33 for a plain member, 15 for a viewer. A real client will run an initialize handshake before this; curl is only here to prove reachability and auth. Then ask the client a read-only question before you let it do anything: how many conversations are open? The server’s own instructions tell it to call get_workspace_overview first, so a correctly connected client answers with your business name and channel list.
Have the client read the dmly://guide/capabilities resource. It is a per-workspace briefing (connected channels, what the plan includes, the tools this particular connection has, and what it cannot do), and it is generated from the live registration, so it never disagrees with tools/list.

Troubleshooting

Everything below is a plain JSON body, not an MCP protocol error, on purpose: a client that cannot get past these never completes a handshake, and a readable HTTP body is what surfaces in its connection dialog. Most come from /mcp itself; the 400 and the first 429 happen earlier, at /oauth/register, while the client is registering itself.
Two possible causes, depending on how it connected.On an API key: usually the key carries mcp:read, and write tools are never registered for a read-scoped key. Generate one at REST API + let an AI assistant read and act and move the client onto it. If it already has that level, the limit is the team role of the member who generated the key, whose permissions the key resolves: a key generated by a custom role gets only that role’s tools, and it loses more if the role is narrowed later. Regenerate it as an admin, or widen that role.Over OAuth: your team role does not grant those tools. A Viewer gets reads only; a Member does not get the team-diary appointment tools by default. Ask an Admin to change your role or give you a custom role with the permissions you need. See Roles and permissions.
It is on a different workspace than you think. The workspace comes from the credential: check which workspace the key was generated in, or (under OAuth) disconnect and reconnect, choosing the right workspace on the consent screen.
The workspace hit its AI outbound budget: an hourly send cap, or a rolling 24-hour cap on how many distinct people an AI client may reach. The refusal names the actual number. It is counted at the send chokepoint, so messages fired by automations off the back of a write count too. Finish the send from the DMLY dashboard.
WhatsApp only allows a freeform message inside 24 hours of the customer’s last message. Outside it, send_template_message with an approved template is the only way through. Read the dmly://reference/templates resource for the exact names and variable counts. See Message templates.
That is correct behaviour. submit_broadcast creates the broadcast awaiting approval and returns sent: false; someone has to approve it in Broadcasts before it goes out. The AI cannot bypass this. See Create a campaign.
On a key: revoke it in Configurations → Integrations → REST API. Access stops on the next request, on /mcp and on the REST API, and it is the only lever with that reach. Two others bite on MCP alone, which is what you want when the key also drives REST integrations you need left running: narrow the role of the member who generated it, and the tools you are worried about stop being registered, or remove them from the workspace, and /mcp refuses the key outright. Both are re-checked on every request, and neither changes what the key can do on /api/v1.Over OAuth, the levers that bite immediately are on the DMLY side: remove the person from the workspace, or narrow their role so the tools you are worried about stop being registered. Both are re-checked on every request. Deleting the connector in the client is worth doing too, but it is not a revocation: DMLY has no page listing OAuth connections and no revocation endpoint, so an access token already issued stays valid until it expires, up to 15 days later.

MCP tools reference

All 37 tools and 4 resources, with arguments, key scope and team permission.

API authentication

The same key, on the REST surface.