Misar IO Docs
MisarMailApi Reference

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]"]
}
FieldTypeRequiredDescription
redirect_urisstring[]YesHTTPS callback URLs. localhost and 127.0.0.1 are allowed for development
client_namestringNoHuman-readable app name shown on the consent screen
grant_typesstring[]NoDefault: ["authorization_code"]
response_typesstring[]NoDefault: ["code"]
scopestringNoSpace-separated requested scopes
logo_uristringNoHTTPS URL to your app's logo
client_uristringNoHTTPS URL to your app's homepage
contactsstring[]NoDeveloper 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 steps

Authorization 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
ParameterRequiredDescription
client_idYesThe client_id from Dynamic Client Registration
redirect_uriYesMust exactly match one of the registered redirect_uris
response_typeYesMust be code
scopeYesSpace-separated list of requested scopes
stateRecommendedCSRF 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
ParameterRequiredDescription
tokenYesThe access token to revoke
token_type_hintNoaccess_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:

  1. The MCP client registers itself dynamically via POST /api/oauth/register
  2. A browser window opens to the authorization endpoint
  3. The user approves access on the MisarMail consent page
  4. The MCP client exchanges the code for a token and stores it locally
  5. 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.