Modulify

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:

Scope Tools
account:read get_account, list_pending_invitations, list_referrals, list_payouts, check_affiliate_code
account:write update_account, update_affiliate
analytics:read get_site_analytics, get_site_analytics_overview, get_analytics_status, get_site_realtime_visitors, check_analytics_installation, query_site_analytics, export_site_analytics
chat:read get_job, list_messages, list_queued_messages, list_code_backups
chat:write send_message, cancel_job, edit_queued_message, reorder_queued_message, resume_queue
code:read list_files, read_file
code:write write_file
comments:read list_comments, get_comment, count_open_comments, list_comment_attachments
comments:write create_comment, reply_to_comment, update_comment, resolve_comment, reopen_comment, delete_comment, move_comment_pin, add_comment_reaction, remove_comment_reaction, upload_comment_attachment, delete_comment_attachment
config:read get_domain_status, get_domain_connect_url, list_secrets, list_site_skills, list_site_connectors, list_site_crons, list_site_cron_runs, preview_cron_schedule, export_site_crons, list_site_webhooks, list_webhook_deliveries, get_webhook_delivery_stats, export_site_webhooks, list_deploy_hooks, list_deploy_hook_calls, get_deploy_hook_call_stats, export_deploy_hooks, get_site_email_settings, get_site_email_usage, list_site_email_sends, get_site_email, get_site_email_stats, list_site_email_suppressions, list_site_email_engagement, list_site_email_credits, get_site_email_credit_stats
config:write add_custom_domain, remove_custom_domain, add_www_domain, remove_www_domain, start_domain_verification, verify_domain, cancel_domain_verification, set_secret, set_secrets, delete_secret, rename_secret, set_site_skill, set_site_connector, toggle_site_cron, run_site_cron, create_site_cron, update_site_cron, delete_site_cron, delete_all_site_crons, delete_site_cron_run, clear_site_cron_runs, toggle_site_webhook, test_site_webhook, create_site_webhook, update_site_webhook, delete_site_webhook, delete_all_site_webhooks, delete_webhook_delivery, clear_webhook_deliveries, toggle_deploy_hook, create_deploy_hook, rename_deploy_hook, delete_deploy_hook, delete_all_deploy_hooks, delete_deploy_hook_call, clear_deploy_hook_calls, toggle_site_emails, update_site_email_settings, send_site_email, send_test_site_email, block_site_email_address, delete_site_email_suppression, add_site_email_domain, check_site_email_domain, remove_site_email_domain, set_site_email_sending_domain, set_site_email_domain_receiving, restart_site_email_domain_verification, get_site_email_domain_connect_url, set_site_email_forwarding, add_site_email_forward_address, resend_site_email_forward_address, remove_site_email_forward_address, delete_site_email_send, clear_site_email_history, rotate_site_email_key
credentials:reveal get_secret, get_all_secrets, get_site_webhook_secret, rotate_site_webhook_secret, get_deploy_hook_url, rotate_deploy_hook_url, get_site_email_key, get_storage_key, rotate_storage_key, get_analytics_key, rotate_analytics_key
data:read get_site_database, list_collections, get_collection_schema, list_rows, list_reference_options, list_enum_options, export_collection, list_storage_files, get_storage_stats, get_storage_info, get_storage_file_url, read_storage_file, browse_storage_backup, read_storage_backup_file, get_storage_restore_status
data:sql execute_sql
data:write create_row, update_row, delete_row, create_site_database, upload_storage_file, create_storage_folder, move_storage_files, delete_storage_files, delete_storage_folder, clear_storage, set_storage_visibility, set_storage_root_visibility, restore_storage_backup
logs:read get_site_logs, get_publish_logs
members:write invite_workspace_member, resend_workspace_invite, cancel_workspace_invite, remove_workspace_member, assign_member_role
publish:write publish_site, unpublish_site, stop_publish, trigger_deploy_hook
sites:create create_site, duplicate_site
sites:delete delete_site, delete_folder, move_folder_to_workspace, remove_queued_message, clear_site_chat, clear_collection, clear_site_database, delete_backup, restore_code_backup, delete_storage_backup, clear_storage_backups, delete_database_backup, clear_database_backups, clear_site_analytics
sites:read list_sites, get_site, check_subdomain_available, list_templates, get_ai_preferences, list_folders, check_folder_slug_available, get_publish_status, list_deployments, get_publish_timeline, list_backups, list_storage_backups, list_database_backups
sites:write rename_site, archive_sites, unarchive_sites, set_show_badge, update_ai_preferences, change_subdomain, update_sharing, create_folder, rename_folder, update_folder_slug, move_sites_to_folder, remove_sites_from_folder, reorder_folders, create_backup, restore_backup, create_storage_backup, set_storage_backup_schedule, create_database_backup, set_database_backup_schedule, set_analytics_enabled
workspaces:read list_workspaces, get_workspace, list_workspace_members, check_member_email, get_workspace_credits, get_workspace_credit_usage, list_workspace_credit_activity, get_role_member_counts, get_site_usage
workspaces:write create_workspace, rename_workspace, set_default_workspace, leave_workspace, create_workspace_role, update_workspace_role, delete_workspace_role

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:

  1. The call must point at a workspace, through its workspaceId or the workspace that owns its projectId. A call that points at neither, or at something that does not exist, is refused.
  2. The token must be allowed to reach that workspace.
  3. 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.

Next