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"],
"code_challenge_methods_supported": ["S256"],
"token_endpoint_auth_methods_supported": ["none"],
"scopes_supported": ["read", "write", "send", "send:transactional", "send:marketing", "contacts"],
"service_documentation": "https://docs.misar.io/mail/api"
}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",
"logo_uri": "https://myapp.example.com/logo.png",
"client_uri": "https://myapp.example.com",
"contacts": ["dev@myapp.example.com"]
}| 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",
"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"
}'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',
}),
});
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
&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
&code_challenge_method=S256
| 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 | No | Space-separated list of requested scopes. Defaults to read when omitted |
state | Recommended | CSRF protection token — returned unchanged in the callback |
code_challenge | Recommended | PKCE challenge — base64url-encoded SHA-256 of your code_verifier |
code_challenge_method | No | Only S256 is supported. Defaults to S256 |
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
&code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
The token endpoint also accepts a JSON body with the same fields. Include code_verifier when you sent a code_challenge in Step 1 (PKCE).
Response
{
"access_token": "msk_1a2b3c4d5e6f7g8h9i0j...",
"token_type": "bearer",
"expires_in": 7776000,
"scope": "send contacts"
}The access_token is a standard MisarMail API key — it starts with msk_ and is used exactly like a key created in the dashboard. expires_in is 7776000 seconds (90 days).
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&code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"const params = new URLSearchParams({
grant_type: 'authorization_code',
code: 'auth_code_xyz',
redirect_uri: 'https://myapp.example.com/auth/callback',
client_id: 'dyn_a1b2c3d4e5f6a7b8c9d0e1f2',
code_verifier: 'dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk',
});
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 msk_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=msk_1a2b3c4d5e6f7g8h9i0j...
| Parameter | Required | Description |
|---|---|---|
token | Yes | The access token to revoke |
Response 200 (always succeeds, even if the token was already revoked or does not exist). The body is empty.
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.