OAuth 2.0
MisarMail OAuth 2.0 server — Dynamic Client Registration (RFC 7591) and authorization code flow for third-party integrations
Overview
MisarMail implements a standards-compliant OAuth 2.0 authorization server (RFC 6749) with:
- Dynamic Client Registration — RFC 7591 (no pre-approval required)
- Authorization Server Metadata — RFC 8414
- Authorization code flow with PKCE support
This is the authentication mechanism used by the MisarMail MCP server and any third-party application that needs to act on behalf of a MisarMail user without handling their API key directly.
If you are building an integration for your own account only, use a regular API key instead. OAuth is designed for apps that serve multiple MisarMail users.
Authorization Server Metadata
Returns the OAuth 2.0 Authorization Server Metadata document (RFC 8414). Clients can discover all endpoint URLs from this document without hardcoding them.
GET /.well-known/oauth-authorization-server
No authentication required.
Response
{
"issuer": "https://api.misar.io/mail",
"authorization_endpoint": "https://api.misar.io/mail/api/oauth/authorize",
"token_endpoint": "https://api.misar.io/mail/api/oauth/token",
"registration_endpoint": "https://api.misar.io/mail/api/oauth/register",
"revocation_endpoint": "https://api.misar.io/mail/api/oauth/revoke",
"response_types_supported": ["code"],
"grant_types_supported": ["authorization_code"],
"token_endpoint_auth_methods_supported": ["none"],
"scopes_supported": ["send", "contacts", "lists", "analytics", "monetization", "send:transactional"]
}Dynamic Client Registration
Register your application as an OAuth client without any manual approval process. This endpoint is publicly accessible and requires no authentication (RFC 7591).
POST /api/oauth/register
Request body
{
"client_name": "My Newsletter App",
"redirect_uris": ["https://myapp.example.com/auth/callback"],
"grant_types": ["authorization_code"],
"response_types": ["code"],
"scope": "send contacts lists",
"logo_uri": "https://myapp.example.com/logo.png",
"client_uri": "https://myapp.example.com",
"contacts": ["[email protected]"]
}| Field | Type | Required | Description |
|---|---|---|---|
redirect_uris | string[] | Yes | HTTPS callback URLs. localhost and 127.0.0.1 are allowed for development |
client_name | string | No | Human-readable app name shown on the consent screen |
grant_types | string[] | No | Default: ["authorization_code"] |
response_types | string[] | No | Default: ["code"] |
scope | string | No | Space-separated requested scopes |
logo_uri | string | No | HTTPS URL to your app's logo |
client_uri | string | No | HTTPS URL to your app's homepage |
contacts | string[] | No | Developer contact emails |
Response 201
{
"client_id": "dyn_a1b2c3d4e5f6a7b8c9d0e1f2",
"client_name": "My Newsletter App",
"redirect_uris": ["https://myapp.example.com/auth/callback"],
"grant_types": ["authorization_code"],
"response_types": ["code"],
"scope": "send contacts lists",
"token_endpoint_auth_method": "none"
}The client_id follows the format dyn_<uuid-without-dashes> and is generated server-side. Store it — you will need it for all subsequent OAuth requests.
redirect_uris must use HTTPS in production. Plain HTTP is only permitted for localhost and 127.0.0.1 during development. Requests with HTTP redirect URIs for other hosts are rejected with 400 Bad Request.
curl -X POST https://api.misar.io/mail/api/oauth/register \
-H "Content-Type: application/json" \
-d '{
"client_name": "My Newsletter App",
"redirect_uris": ["https://myapp.example.com/auth/callback"],
"scope": "send contacts lists"
}'const res = await fetch('https://api.misar.io/mail/api/oauth/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
client_name: 'My Newsletter App',
redirect_uris: ['https://myapp.example.com/auth/callback'],
scope: 'send contacts lists',
}),
});
const { client_id } = await res.json();
// Store client_id for use in authorize/token stepsAuthorization Code Flow
Step 1 — Redirect the user to the authorization endpoint
Redirect the user's browser to the authorization endpoint with your client details. The user will see a consent screen listing the scopes your app is requesting.
GET /api/oauth/authorize
?client_id=dyn_a1b2c3d4e5f6a7b8c9d0e1f2
&redirect_uri=https%3A%2F%2Fmyapp.example.com%2Fauth%2Fcallback
&response_type=code
&scope=send%20contacts
&state=random_csrf_token
| Parameter | Required | Description |
|---|---|---|
client_id | Yes | The client_id from Dynamic Client Registration |
redirect_uri | Yes | Must exactly match one of the registered redirect_uris |
response_type | Yes | Must be code |
scope | Yes | Space-separated list of requested scopes |
state | Recommended | CSRF protection token — returned unchanged in the callback |
Step 2 — User grants or denies access
MisarMail displays a consent screen showing the user which account permissions your app is requesting. If the user approves, they are redirected to your redirect_uri.
On approval:
https://myapp.example.com/auth/callback
?code=auth_code_xyz
&state=random_csrf_token
On denial:
https://myapp.example.com/auth/callback
?error=access_denied
&state=random_csrf_token
Always verify the state parameter matches what you sent in Step 1 before proceeding.
Step 3 — Exchange the code for an access token
Make a server-side request to exchange the authorization code for an access token. Codes expire after 10 minutes.
POST /api/oauth/token
Content-Type: application/x-www-form-urlencoded
Request body (form-encoded)
grant_type=authorization_code
&code=auth_code_xyz
&redirect_uri=https%3A%2F%2Fmyapp.example.com%2Fauth%2Fcallback
&client_id=dyn_a1b2c3d4e5f6a7b8c9d0e1f2
Response
{
"access_token": "oat_1a2b3c4d5e6f7g8h9i0j",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "send contacts"
}curl -X POST https://api.misar.io/mail/api/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code&code=auth_code_xyz&redirect_uri=https%3A%2F%2Fmyapp.example.com%2Fauth%2Fcallback&client_id=dyn_a1b2c3d4e5f6a7b8c9d0e1f2"const params = new URLSearchParams({
grant_type: 'authorization_code',
code: 'auth_code_xyz',
redirect_uri: 'https://myapp.example.com/auth/callback',
client_id: 'dyn_a1b2c3d4e5f6a7b8c9d0e1f2',
});
const res = await fetch('https://api.misar.io/mail/api/oauth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: params.toString(),
});
const { access_token } = await res.json();Step 4 — Use the access token
Include the access token in the Authorization header exactly as you would with an API key.
curl https://api.misar.io/mail/v1/campaigns \
-H "Authorization: Bearer oat_1a2b3c4d5e6f7g8h9i0j"The token is scoped to the permissions the user granted in Step 2. Requests that exceed the granted scopes return 403 Forbidden.
Revoke a Token
Revoke an access token when the user disconnects your app or logs out. Revoked tokens are immediately invalidated.
POST /api/oauth/revoke
Content-Type: application/x-www-form-urlencoded
Request body (form-encoded)
token=oat_1a2b3c4d5e6f7g8h9i0j
&token_type_hint=access_token
| Parameter | Required | Description |
|---|---|---|
token | Yes | The access token to revoke |
token_type_hint | No | access_token or refresh_token — helps the server resolve the token faster |
Response 200 (always succeeds, even if the token was already revoked or does not exist)
{}MCP Server Usage
The MisarMail MCP server uses this OAuth flow to let Claude Desktop and other MCP clients (Smithery, Cursor) authenticate on behalf of a user. When a user adds the MCP server:
- The MCP client registers itself dynamically via
POST /api/oauth/register - A browser window opens to the authorization endpoint
- The user approves access on the MisarMail consent page
- The MCP client exchanges the code for a token and stores it locally
- All subsequent MCP tool calls use the access token — the user's API key is never exposed
This means users can grant MCP access to their MisarMail account with a single browser click, without ever copying an API key.