Authentication
How a request proves who it is, what a token's scopes allow, and which workspaces it can reach.
On this page
Every request to the API, except GET /v1/openapi.json, carries an access token. The API uses the same tokens as the MCP server: a token you created for an AI client works here too, and the other way round.
Send the token
Put the token in the Authorization header, after the word Bearer:
curl -X POST https://api.modulify.ai/v1/list_workspaces \
-H "Authorization: Bearer YOUR_TOKEN"The header is also accepted with the bare token and no Bearer in front of it.
Never put a token in a URL. A request whose address contains one is refused with a 400 reading "Send the access token in the Authorization header, never in the URL. Revoke this token and create a new one, since URLs end up in logs!" Addresses are written to logs along the way, so treat that token as leaked and replace it.
Create a token
Tokens live on the Tokens page, reached from the user menu in the dashboard at /dashboard/tokens. Create token asks for:
| Field | Rule |
|---|---|
| Token name | Required, up to 60 characters. Name it after the script or service that will use it |
| Expires after | 30, 90 or 180 days, or 1 year. Defaults to 90 days and never changes afterwards |
| Which workspaces it can reach | Every workspace you are a member of, or only the ones you tick. Shown when you belong to more than one |
| What this token can do | The scopes. At least one must be ticked |
The value is shown once and starts with mdf_mcp_. Modulify keeps only a hash of it, so a lost token cannot be read back: revoke it and create another. You can hold 10 active tokens at a time.
On the same page, open Connecting a client and choose the HTTP API tab for a ready-made curl call with the right address.
Check a token
GET /v1/token answers with what a token is, without running any method:
curl https://api.modulify.ai/v1/token \
-H "Authorization: Bearer YOUR_TOKEN"{
"success": true,
"message": "...",
"data": {
"id": "6710f0c2a1b2c3d4e5f60718",
"name": "Nightly content sync",
"scopes": ["workspaces:read", "sites:read", "chat:read", "chat:write"],
"workspaces": [],
"expiresAt": "2027-01-06T09:30:00.000Z",
"rateLimit": { "limit": 240, "remaining": 238, "resetSeconds": 41 }
},
"code": 200,
"version": "0.0.1740"
}An empty workspaces list means every workspace you belong to. It needs no scope and does not count against the token's per-minute budget, so a script can call it on start to fail early with a clear reason.
Scopes
A scope unlocks a set of methods. Each method's page names the one it needs, and GET /v1/tools lists only the methods the token's scopes allow. Calling a method outside them is refused with a 403 that names the missing scope.
The scopes are the same ones the MCP server uses, with the same defaults. Tokens and scopes explains what each one permits and why ten of them are off by default. The table below is built from the method pages:
Grant a token only what its job needs. A script that reads analytics every night needs analytics:read and sites:read, not sites:delete.
Workspaces
A token reaches every workspace you are a member of unless you narrowed it to some of them. Every method that works inside a workspace says which workspace or site it acts on, and three checks run before it does:
- The call must point at a workspace, through its
workspaceIdor the workspace that owns itsprojectId. A call that points at neither, or at something that does not exist, is refused. - The token must be allowed to reach that workspace.
- Your role in that workspace must hold the API access permission, described in the product as "Reach this workspace with a token from an AI client". Owners hold every permission. See Members and roles.
A refusal from any of them is a 403. A few methods belong to no workspace, such as list_workspaces, create_workspace and the account methods, which act only on the account the token belongs to.
When a token stops working
| Answer | What it means |
|---|---|
401 "An access token is required. Send it in the Authorization header as Bearer followed by the token!" |
No token arrived. Check the header name and that the value is not empty |
401 "This access token is invalid, revoked or expired!" |
The value does not match a live token. It was cut short, revoked, or it passed its expiry date |
Both answers carry a WWW-Authenticate: Bearer realm="modulify" header, and the second adds error="invalid_token". Revoking a token from the Tokens page takes effect at once, for the API and for MCP alike. An expired token cannot be extended, so create a new one.