Skip to content
machinemail

Dashboard Auth

Cookie sessions for the dashboard.

Note

These endpoints serve the MachineMail dashboard. They authenticate with the mm_session cookie, not with an API key. They are listed for completeness; integrations should use API keys.

MethodPathSummary
GET/auth/authkitLegacy Sign-In Redirect (Deprecated)
GET/auth/callbackSign-in Callback
GET/auth/claimPreview Claim
POST/auth/claimComplete Claim
GET/auth/configSign-in Options
POST/auth/email/startSend Sign-in Code
POST/auth/email/verifyVerify Sign-in Code
GET/auth/first-keyGet First API Key
GET/auth/invitations/{token}Preview Invitation
POST/auth/loginLog In (Deprecated)
POST/auth/logoutLog Out
GET/auth/magicRedeem Magic Link
POST/auth/magic-linkRequest Magic Link
GET/auth/meCurrent User
GET/auth/oauth/authorizePreview App Authorization
POST/auth/oauth/authorize/approveApprove App Authorization
POST/auth/oauth/authorize/denyDeny App Authorization
GET/auth/organizationsList My Organizations
POST/auth/organizationsCreate Organization
POST/auth/organizations/switchSwitch Organization
GET/auth/pendingGet Sign-in in Progress
POST/auth/pending/organizationChoose Organization
POST/auth/pending/verifyVerify Email
POST/auth/signupSign Up (Deprecated)
GET/auth/social/{provider}Sign In with a Social Provider

Legacy Sign-In Redirect (Deprecated)

http
GET /auth/authkit

Warning

Deprecated.

Deprecated: an old sign-in entry point, kept so existing links work. Redirects to the sign-in page, /login (keeping a same-origin next).

Authentication: None.

Query parameters

nextstring
Same-origin path to land on after sign-in. Default /app.

Response

302 To /login.

Headers:

Locationstring
Where to go next.

Errors

StatusMeaning
400Invalid request: error.type is validation and error.param names the field.
500Unexpected server error.

Example

Request
curl "https://api.machinemail.dev/auth/authkit"

Sign-in Callback

http
GET /auth/callback

Where the browser returns after a social or SSO sign-in. Checks state against the browser’s cookie, exchanges the code, links the sign-in to the MachineMail account with the same verified email (or creates one), syncs their organizations, sets the mm_session cookie and redirects to next. A person with no organization gets one, with a first API key (welcome=1, GET /auth/first-key).

When the sign-in needs one more step, the browser goes to our own screens with a mm_signin cookie: /login/organization (pick an organization) or /login/verify (verify the email with a code), or on to the organization’s SSO connection.

Failures redirect to /login?error=signin_failed (the sign-in was refused or failed), access_denied (cancelled at the provider), unverified (an unverified email matching an existing account), linked (the account is already linked to a different sign-in identity), mfa (the sign-in asked for multi-factor authentication, which MachineMail does not support), methods (the organization does not allow this sign-in method) or codes_paused (too many codes for the address recently: a verification code was not sent; try later). A callback without state (one this browser did not start) never signs anyone in: it goes to /login, or to /invite/{token} when it carries an invitation token.

Authentication: None.

Query parameters

codestring
Authorization code from the sign-in.
statestring
The state sent with the authorization request.
errorstring
Error code, when sign-in failed.
error_descriptionstring
Error description, when sign-in failed.
invitation_tokenstring
Invitation token, on a sign-in started from an invitation email.

Response

302 Signed in (Set-Cookie carries mm_session), our next screen, or back to /login with an error.

Headers:

Locationstring
Where to go next.
Set-Cookiestring
mm_session=<token>; HttpOnly; SameSite=Lax; Path=/ (plus Secure on https).

Errors

StatusMeaning
400Invalid request: error.type is validation and error.param names the field.
500Unexpected server error.

Example

Request
curl "https://api.machinemail.dev/auth/callback"

Preview Claim

http
GET /auth/claim

Called by the dashboard /claim page for a signed-in human before confirming. Pass user_code or claim_attempt_token. Returns the registration (its agent-described name is untrusted), the claim email and the sandbox inbox. 403 when the registration is bound to a different email than the signed-in user; 429 after too many lookups (per user, 10 minute window).

Authentication: the dashboard mm_session cookie.

Query parameters

user_codestring
The 6-digit code the agent shows; spaces and dashes are ignored.
claim_attempt_tokenstring
The cat_... token from verification_uri.

Response

200 The pending registration (application/json)

agent_idstringRequired
ID of the registration (reg_...).
registration_idstringRequired
Same value as agent_id.
registration_type"anonymous" | "service_auth" | "identity_assertion"Required
anonymous: no human yet; a sandbox inbox until a human claims it. service_auth: on behalf of a named user, who confirms first. identity_assertion: an ID-JAG from a trusted agent provider.
status"pending_claim" | "claimed" | "revoked"Required
namestring
Agent-described label. Untrusted.
clientstring
Provider name for identity_assertion, otherwise agent (self-described) or anonymous agent.
organization_idstring
Organization the registration is bound to (after a claim, or right away for identity_assertion).
inbox_idstring
The sandbox inbox of an anonymous registration.
emailstring
Verified email bound at claim or carried by the identity assertion.
issuerstring
Issuer of the identity assertion (identity_assertion).
agent_platformstring
agent_platform claim of the identity assertion.
created_atstring (date-time)Required
When the agent registered.
claimed_atstring (date-time)
When a human claimed the registration.
revoked_atstring (date-time)
When the registration was revoked.
expires_atstring (date-time)
When an unclaimed registration expires.
claim_emailstring
Only this user may claim the registration.
user_code_expires_atstring (date-time)
When the code expires.
sandbox_inboxobject
The sandbox inbox of an anonymous registration.

Errors

StatusMeaning
400Invalid request: error.type is validation and error.param names the field.
401Missing, invalid, expired or revoked credential.
403The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account_suspended: error.message says what still works; do not retry or re-register).
404Not found. Also returned for resources outside the credential scope.
429Rate limited.
500Unexpected server error.

Error bodies use the error envelope.

Example

Request
curl "https://api.machinemail.dev/auth/claim" \
  -H "Cookie: mm_session=$SESSION"
Response 200
{
  "agent_id": "string",
  "registration_id": "string",
  "registration_type": "anonymous",
  "status": "pending_claim",
  "name": "Production",
  "client": "string",
  "organization_id": "org_01k6x2m9qd0000a7h3kq9v2c4e",
  "inbox_id": "agent@machinemail.dev",
  "email": "agent@machinemail.dev",
  "issuer": "string",
  "agent_platform": "string",
  "created_at": "2026-10-01T12:00:00.000Z",
  "claimed_at": "2026-10-01T12:00:00.000Z",
  "revoked_at": "2026-10-01T12:00:00.000Z",
  "expires_at": "2026-10-01T12:00:00.000Z",
  "claim_email": "agent@machinemail.dev",
  "user_code_expires_at": "2026-10-01T12:00:00.000Z",
  "sandbox_inbox": {
    "pod_id": "pod_01k6x2m9qd0001b8j4mr0w3d5f",
    "inbox_id": "agent@machinemail.dev",
    "email": "agent@machinemail.dev",
    "display_name": "Support Agent",
    "client_id": "my-client-id",
    "status": "active",
    "metadata": {
      "key": "value"
    },
    "updated_at": "2026-10-01T12:00:00.000Z",
    "created_at": "2026-10-01T12:00:00.000Z",
    "organization_id": "org_01k6x2m9qd0000a7h3kq9v2c4e",
    "ephemeral": false,
    "ttl_seconds": 1,
    "expires_at": "2026-10-01T12:00:00.000Z",
    "created_by": "api"
  }
}

Complete Claim

http
POST /auth/claim

Called by the dashboard /claim page for a signed-in human. Binds the agent registration to their organization, moves the sandbox inbox into their default pod (made permanent) and invalidates pre-claim tokens. 403 when the signed-in user does not match the claim email; 429 after too many attempts (per user, 10 minute window).

Authentication: the dashboard mm_session cookie.

Request body

Content type: application/json.

user_codestringRequired
The 6-digit code the agent shows; spaces and dashes are ignored.
claim_attempt_tokenstring
The cat_... token from verification_uri; when given, it must belong to the same attempt as user_code.

Response

200 Claimed (application/json)

agent_idstringRequired
ID of the registration (reg_...).
registration_idstringRequired
Same value as agent_id.
registration_type"anonymous" | "service_auth" | "identity_assertion"Required
anonymous: no human yet; a sandbox inbox until a human claims it. service_auth: on behalf of a named user, who confirms first. identity_assertion: an ID-JAG from a trusted agent provider.
status"claimed"Required
namestring
Agent-described label. Untrusted.
clientstring
Provider name for identity_assertion, otherwise agent (self-described) or anonymous agent.
organization_idstringRequired
The organization of the signed-in user.
inbox_idstring
The sandbox inbox of an anonymous registration.
emailstring
Verified email bound at claim or carried by the identity assertion.
issuerstring
Issuer of the identity assertion (identity_assertion).
agent_platformstring
agent_platform claim of the identity assertion.
created_atstring (date-time)Required
When the agent registered.
claimed_atstring (date-time)
When a human claimed the registration.
revoked_atstring (date-time)
When the registration was revoked.
expires_atstring (date-time)
When an unclaimed registration expires.
inboxobject
The former sandbox inbox, now permanent and in the user’s default pod.

Errors

StatusMeaning
400Invalid request: error.type is validation and error.param names the field.
401Missing, invalid, expired or revoked credential.
403The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account_suspended: error.message says what still works; do not retry or re-register).
404Not found. Also returned for resources outside the credential scope.
429Rate limited.
500Unexpected server error.

Error bodies use the error envelope.

Example

Request
curl -X POST "https://api.machinemail.dev/auth/claim" \
  -H "Cookie: mm_session=$SESSION" \
  -H "Content-Type: application/json" \
  -d '{
  "user_code": "string"
}'
Response 200
{
  "agent_id": "string",
  "registration_id": "string",
  "registration_type": "anonymous",
  "status": "claimed",
  "name": "Production",
  "client": "string",
  "organization_id": "org_01k6x2m9qd0000a7h3kq9v2c4e",
  "inbox_id": "agent@machinemail.dev",
  "email": "agent@machinemail.dev",
  "issuer": "string",
  "agent_platform": "string",
  "created_at": "2026-10-01T12:00:00.000Z",
  "claimed_at": "2026-10-01T12:00:00.000Z",
  "revoked_at": "2026-10-01T12:00:00.000Z",
  "expires_at": "2026-10-01T12:00:00.000Z",
  "inbox": {
    "pod_id": "pod_01k6x2m9qd0001b8j4mr0w3d5f",
    "inbox_id": "agent@machinemail.dev",
    "email": "agent@machinemail.dev",
    "display_name": "Support Agent",
    "client_id": "my-client-id",
    "status": "active",
    "metadata": {
      "key": "value"
    },
    "updated_at": "2026-10-01T12:00:00.000Z",
    "created_at": "2026-10-01T12:00:00.000Z",
    "organization_id": "org_01k6x2m9qd0000a7h3kq9v2c4e",
    "ephemeral": false,
    "ttl_seconds": 1,
    "expires_at": "2026-10-01T12:00:00.000Z",
    "created_by": "api"
  }
}

Sign-in Options

http
GET /auth/config

How people sign in to this server: email codes (POST /auth/email/start), social providers (GET /auth/social/{provider}) and SSO, all on MachineMail’s own pages; on a server without them (self-hosted or development), magic links (POST /auth/magic-link).

Authentication: None.

Response

200 Sign-in options (application/json)

workosbooleanRequired
Legacy alias of email_code, kept for older clients: sign in with an email code (POST /auth/email/start), a social provider (GET /auth/social/{provider}) or SSO.
email_codebooleanRequired
Email-code sign-in (POST /auth/email/start, then POST /auth/email/verify) is available.
providers("google" | "github" | "apple" | "microsoft")[]Required
Social providers on offer (GET /auth/social/{provider}). Empty when this server offers none.
magic_linkbooleanRequired
Local magic-link sign-in (POST /auth/magic-link) is available.
passwordbooleanRequired
The deprecated password endpoints are available.
authkitbooleanRequiredDeprecated
Deprecated: always false. Kept for older clients; see email_code.

Errors

StatusMeaning
500Unexpected server error.

Example

Request
curl "https://api.machinemail.dev/auth/config"
Response 200
{
  "workos": false,
  "email_code": false,
  "providers": ["google"],
  "magic_link": false,
  "password": false,
  "authkit": false
}

Send Sign-in Code

http
POST /auth/email/start

Starts an email-code sign-in: emails a 6-digit code, valid 10 minutes, and sets the mm_signin cookie that POST /auth/email/verify needs. Sending it again sends a new code. The answer is the same whether or not the address has an account; a malformed address is 400, and an unknown or used invitation_token is 404.

SSO discovery first: when the address’s domain is verified by an organization with an active SSO connection, no code is sent and redirect_to is that connection.

With an invitation_token and no email, the code goes to the invited address; signing in accepts the invitation.

dev_code (the code itself) only in development with the noop transport, or with MACHINEMAIL_DEV_MAGIC_LINK=true; never otherwise in production.

429 also while email codes for the address are paused for this client IP (too many wrong codes from it; see POST /auth/email/verify), when this client IP (an IPv6 client by its /64) has had 5 new codes for the address in 15 minutes, and when the address has had 10 new codes in the last hour from any IP, counting verification codes. These limits are kept in the database (every machine) and only hold back new codes: a code already sent can still be used.

404 when this server does not offer email-code sign-in (GET /auth/config).

Rate limited per client IP and per email address (5 codes per 15 minutes per address and client IP; 10 per hour per address): 429 with Retry-After.

A request whose Origin is another site is refused (403).

Authentication: None.

Request body

Content type: application/json.

emailstring
Address to send the code to. Optional with invitation_token (the code goes to the invited address).
nextstring
Same-origin path to land on after sign-in, for example /cli?code=ABCD-EFGH. Anything else is ignored. Default /app.
invitation_tokenstring
An invitation token (from /invite/{token}): signing in accepts the invitation.

Response

202 Code sent (if the address can receive mail), or the SSO redirect (application/json)

Headers:

Set-Cookiestring
mm_signin=<handle>; HttpOnly; SameSite=Lax; Path=/auth (the sign-in in progress, 10 minutes), and mm_session once signed in.
oktrueRequired
redirect_tostring
Set when the address’s domain signs in with SSO: send the browser here (the organization’s identity provider). Otherwise a code was emailed: show the code screen.
dev_codestring
Development only (noop transport or MACHINEMAIL_DEV_MAGIC_LINK=true): the code itself.

Errors

StatusMeaning
400Invalid request: error.type is validation and error.param names the field.
403The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account_suspended: error.message says what still works; do not retry or re-register).
404Not found. Also returned for resources outside the credential scope.
429Rate limited.
500Unexpected server error.

Example

Request
curl -X POST "https://api.machinemail.dev/auth/email/start" \
  -H "Content-Type: application/json" \
  -d '{
  "email": "agent@machinemail.dev"
}'
Response 202
{
  "ok": true,
  "redirect_to": "string",
  "dev_code": "string"
}

Verify Sign-in Code

http
POST /auth/email/verify

Checks the emailed code for the sign-in in the mm_signin cookie. Signed in: sets mm_session and redirect_to is next. The sign-in may need another step first (signed_in: false, redirect_to is our organization picker, the email verification screen or SSO).

A wrong code is 400 (param code); after 5 wrong codes, or when the code has expired or was already used, there is no sign-in any more: 404, send a new code. An invitation revoked, expired or accepted since the code was sent is 404 too. Other failures answer redirect_to: /login?error=<code>.

20 wrong codes for one address from one client IP (an IPv6 client by its /64) within 24 hours (counted in the database, across codes and machines) pause email codes for that address from that IP: this endpoint and POST /auth/email/start answer 429 to it until the oldest is a day old, and the address is emailed. Other IPs are not affected, so guessing cannot lock the person out of their right code.

Rate limited per client IP and per email address (5 attempts, then 1 per minute, per address and client IP): 429 with Retry-After.

A request whose Origin is another site is refused (403).

Authentication: None.

Request body

Content type: application/json.

codestringRequired
The 6-digit code from the email. Spaces and dashes are ignored.

Response

200 Signed in, or the next step (application/json)

Headers:

Set-Cookiestring
mm_signin=<handle>; HttpOnly; SameSite=Lax; Path=/auth (the sign-in in progress, 10 minutes), and mm_session once signed in.
oktrueRequired
signed_inbooleanRequired
true: the mm_session cookie is set. false: another step comes first (organization picker, email verification, SSO).
redirect_tostringRequired
Where to send the browser next, built by the server: next (with welcome=1 after a first sign-in), /login/organization, /login/verify, an identity provider, or /login?error=<code>.

Errors

StatusMeaning
400Invalid request: error.type is validation and error.param names the field.
403The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account_suspended: error.message says what still works; do not retry or re-register).
404Not found. Also returned for resources outside the credential scope.
429Rate limited.
500Unexpected server error.

Example

Request
curl -X POST "https://api.machinemail.dev/auth/email/verify" \
  -H "Content-Type: application/json" \
  -d '{
  "code": "string"
}'
Response 200
{
  "ok": true,
  "signed_in": false,
  "redirect_to": "string"
}

Get First API Key

http
GET /auth/first-key

The organization API key created on first sign-in, with its plaintext secret. Returned once: the record is deleted on read and expires 10 minutes after sign-in; 404 afterwards.

Authentication: the dashboard mm_session cookie.

Response

200 The key, shown once (application/json)

api_keyOneTimeApiKeyRequired
An API key with its plaintext secret, returned once.

api_key is a OneTimeApiKey:

api_key_idstringRequired
ID of the API key.
api_keystringRequired
Plaintext secret (mm_live_...). Shown once.
prefixstringRequired
First characters of the key, shown in listings.
namestringRequired
Name of the key.
created_atstring (date-time)Required
When the key was created.

Errors

StatusMeaning
401Missing, invalid, expired or revoked credential.
404Not found. Also returned for resources outside the credential scope.
500Unexpected server error.

Error bodies use the error envelope.

Example

Request
curl "https://api.machinemail.dev/auth/first-key" \
  -H "Cookie: mm_session=$SESSION"
Response 200
{
  "api_key": {
    "api_key_id": "key_01k6x3e8mr0000h4r0ty6c9k1n",
    "api_key": "mm_live_4f8Kq2ZbX9mN3pR7tV1wY6cH0jL5sD8a",
    "prefix": "mm_live_4f8K",
    "name": "Production",
    "created_at": "2026-10-01T12:00:00.000Z"
  }
}

Preview Invitation

http
GET /auth/invitations/{token}

What an invitation link (/invite/{token}) is for: the organization, the invited address (masked) and the state. Accept it by signing in with invitation_token (POST /auth/email/start or GET /auth/social/{provider}). 404 for an unknown token, or when this server does not use invitation links. 429 with Retry-After when the lookup is rate limited, or cannot be answered right now (an outage): retry after that many seconds.

Authentication: None.

Path parameters

tokenstringRequired
The token from the invitation link (/invite/{token}).

Response

200 The invitation (application/json)

organization_namestringRequired
The organization the invitation is to.
emailstringRequired
The invited address, masked (a••@example.com).
state"pending" | "accepted" | "expired" | "revoked"Required
Only a pending invitation can be accepted.
expires_atstring (date-time)Required
When the invitation expires.

Errors

StatusMeaning
404Not found. Also returned for resources outside the credential scope.
429Rate limited.
500Unexpected server error.

Example

Request
curl "https://api.machinemail.dev/auth/invitations/string"
Response 200
{
  "organization_name": "string",
  "email": "agent@machinemail.dev",
  "state": "pending",
  "expires_at": "2026-10-01T12:00:00.000Z"
}

Log In (Deprecated)

http
POST /auth/login

Warning

Deprecated.

Deprecated: the dashboard signs in with POST /auth/magic-link. Sets the mm_session cookie.

Off in production unless the server sets MACHINEMAIL_PASSWORD_AUTH=true: 404 otherwise.

Rate limited per client IP and per email address (5 attempts, then 1 per minute; a successful sign-in resets the address): 429 with Retry-After.

Authentication: None.

Request body

Content type: application/json.

emailstringRequired
passwordstringRequired

Response

200 Signed in (application/json)

Headers:

Set-Cookiestring
mm_session=<token>; HttpOnly; SameSite=Lax; Path=/ (plus Secure on https).
userUserRequired
A person who signs in to the dashboard.
organizationOrganizationRequired
The organization of the credential. There are no inbox or domain quotas to report (inbox_limit, domain_limit) and no billing fields.

user is a User:

user_idstringRequired
ID of the user.
emailstringRequired
Email address the user signs in with.
namestring | null
Display name, when set.
organization_idstringRequired
ID of the organization this record is about: the session’s active organization, or the organization listed.
role"owner" | "admin" | "member"Required
Role in the organization. Owners and admins invite, change roles and remove members; only owners grant or remove the owner role, and an organization always keeps one owner.
created_atstring (date-time)Required
When the user was created.

organization is a Organization:

organization_idstringRequired
ID of the organization.
namestringRequired
Name of the organization.
slugstringRequired
URL-safe name of the organization.
plan"free" | "pro" | "enterprise"Required
Plan of the organization.
inbox_countintegerRequired
Number of inboxes in the credential scope.
domain_countintegerRequired
Number of domains in the organization.
usageobject
Usage. Always present on GET /v0/organizations and PATCH /v0/organizations.
shared_domainstring
Domain every organization can create inboxes on, used when an inbox is created without domain. Always present on GET /v0/organizations and PATCH /v0/organizations.
updated_atstring (date-time)Required
Time at which the organization was last updated.
created_atstring (date-time)Required
Time at which the organization was created.

Errors

StatusMeaning
400Invalid request: error.type is validation and error.param names the field.
401Missing, invalid, expired or revoked credential.
404Not found. Also returned for resources outside the credential scope.
429Rate limited.
500Unexpected server error.

Example

Request
curl -X POST "https://api.machinemail.dev/auth/login" \
  -H "Content-Type: application/json" \
  -d '{
  "email": "agent@machinemail.dev",
  "password": "string"
}'
Response 200
{
  "user": {
    "user_id": "usr_01k6x3j0rw0000n8w4yc0g3q5s",
    "email": "agent@machinemail.dev",
    "name": "Production",
    "organization_id": "org_01k6x2m9qd0000a7h3kq9v2c4e",
    "role": "owner",
    "created_at": "2026-10-01T12:00:00.000Z"
  },
  "organization": {
    "organization_id": "org_01k6x2m9qd0000a7h3kq9v2c4e",
    "name": "Production",
    "slug": "string",
    "plan": "free",
    "inbox_count": 1,
    "domain_count": 1,
    "usage": {
      "message_count": 1,
      "storage_bytes": 2048,
      "messages_received_30d": 1,
      "messages_sent_30d": 1
    },
    "shared_domain": "string",
    "updated_at": "2026-10-01T12:00:00.000Z",
    "created_at": "2026-10-01T12:00:00.000Z"
  }
}

Log Out

http
POST /auth/logout

Ends the session in the mm_session cookie, if any, and clears the cookie. Succeeds without a session too.

Authentication: None.

Response

200 Signed out (application/json)

oktrueRequired

Errors

StatusMeaning
500Unexpected server error.

Example

Request
curl -X POST "https://api.machinemail.dev/auth/logout"
Response 200
{
  "ok": true
}
http
GET /auth/magic

The link in the sign-in email. Consumes the token (single use), sets the mm_session cookie and redirects to next (default /app). On a first sign-in it creates the user, an organization, a Default Pod and a first organization API key, and adds welcome=1 to the redirect; fetch that key once with GET /auth/first-key.

Authentication: None.

Query parameters

tokenstring
The token from the emailed link. A missing, unknown, used or expired token redirects to /login?error=expired.

Response

302 Signed in: Location is next and Set-Cookie carries mm_session. Otherwise Location is /login?error=expired.

Headers:

Locationstring
Where to go next.
Set-Cookiestring
mm_session=<token>; HttpOnly; SameSite=Lax; Path=/ (plus Secure on https).

Errors

StatusMeaning
400Invalid request: error.type is validation and error.param names the field.
500Unexpected server error.

Example

Request
curl "https://api.machinemail.dev/auth/magic"
http
POST /auth/magic-link

Emails a single-use sign-in link (<PUBLIC_URL>/auth/magic?token=..., valid 15 minutes). Always 202 {"ok": true} for a well-formed email, whether or not it has an account; a malformed address is 400.

Development only (noop transport, or MACHINEMAIL_DEV_MAGIC_LINK=true): the body also carries dev_link.

Off (404) when email-code sign-in is on (sign in with an email code instead), unless the server sets MACHINEMAIL_MAGIC_LINK=true.

Rate limited per client IP and per email address (5 links per 15 minutes per address): 429 with Retry-After.

Authentication: None.

Request body

Content type: application/json.

emailstringRequired
Address to send the sign-in link to.
org_namestring
Organization name, used only when this is the address’s first sign-in.
nextstring
Same-origin path to land on after sign-in, for example /claim?user_code=123456. Anything else is ignored. Default /app.
organization_namestring
Alias of org_name.
organizationNamestringDeprecated
Alias of org_name.

Response

202 Accepted. The link was sent if the address can receive mail. (application/json)

oktrueRequired
dev_linkstring
Development only (noop transport or MACHINEMAIL_DEV_MAGIC_LINK=true): the sign-in link itself.

Errors

StatusMeaning
400Invalid request: error.type is validation and error.param names the field.
404Not found. Also returned for resources outside the credential scope.
429Rate limited.
500Unexpected server error.

Example

Request
curl -X POST "https://api.machinemail.dev/auth/magic-link" \
  -H "Content-Type: application/json" \
  -d '{
  "email": "agent@machinemail.dev"
}'
Response 202
{
  "ok": true,
  "dev_link": "string"
}

Current User

http
GET /auth/me

The signed-in user and the organization the session is on; user.role is their role there. For what an API key can see, use GET /v0/auth/me.

Authentication: the dashboard mm_session cookie.

Response

200 The session (application/json)

userUserRequired
A person who signs in to the dashboard.
organizationOrganizationRequired
The organization of the credential. There are no inbox or domain quotas to report (inbox_limit, domain_limit) and no billing fields.

user is a User:

user_idstringRequired
ID of the user.
emailstringRequired
Email address the user signs in with.
namestring | null
Display name, when set.
organization_idstringRequired
ID of the organization this record is about: the session’s active organization, or the organization listed.
role"owner" | "admin" | "member"Required
Role in the organization. Owners and admins invite, change roles and remove members; only owners grant or remove the owner role, and an organization always keeps one owner.
created_atstring (date-time)Required
When the user was created.

organization is a Organization:

organization_idstringRequired
ID of the organization.
namestringRequired
Name of the organization.
slugstringRequired
URL-safe name of the organization.
plan"free" | "pro" | "enterprise"Required
Plan of the organization.
inbox_countintegerRequired
Number of inboxes in the credential scope.
domain_countintegerRequired
Number of domains in the organization.
usageobject
Usage. Always present on GET /v0/organizations and PATCH /v0/organizations.
shared_domainstring
Domain every organization can create inboxes on, used when an inbox is created without domain. Always present on GET /v0/organizations and PATCH /v0/organizations.
updated_atstring (date-time)Required
Time at which the organization was last updated.
created_atstring (date-time)Required
Time at which the organization was created.

Errors

StatusMeaning
401Missing, invalid, expired or revoked credential.
500Unexpected server error.

Error bodies use the error envelope.

Example

Request
curl "https://api.machinemail.dev/auth/me" \
  -H "Cookie: mm_session=$SESSION"
Response 200
{
  "user": {
    "user_id": "usr_01k6x3j0rw0000n8w4yc0g3q5s",
    "email": "agent@machinemail.dev",
    "name": "Production",
    "organization_id": "org_01k6x2m9qd0000a7h3kq9v2c4e",
    "role": "owner",
    "created_at": "2026-10-01T12:00:00.000Z"
  },
  "organization": {
    "organization_id": "org_01k6x2m9qd0000a7h3kq9v2c4e",
    "name": "Production",
    "slug": "string",
    "plan": "free",
    "inbox_count": 1,
    "domain_count": 1,
    "usage": {
      "message_count": 1,
      "storage_bytes": 2048,
      "messages_received_30d": 1,
      "messages_sent_30d": 1
    },
    "shared_domain": "string",
    "updated_at": "2026-10-01T12:00:00.000Z",
    "created_at": "2026-10-01T12:00:00.000Z"
  }
}

Preview App Authorization

http
GET /auth/oauth/authorize

Called by the dashboard /connect page for a signed-in person. Returns the client (its name and logo are self-reported unless it identified itself with a metadata document URL), where the response goes, the resource and the requested scopes. 404 for an unknown, decided or expired request. Dashboard session only: API keys are not accepted.

Authentication: the dashboard mm_session cookie.

Query parameters

request_idstringRequired
The request parameter of the /connect page URL.

Response

200 The pending request (application/json)

request_idstringRequired
clientOAuthClientInfoRequired
redirect_uristringRequired
Where the response goes.
redirect_hoststringRequired
Host (or scheme, for an app link) of redirect_uri, for the human to check.
redirect_loopbackbooleanRequired
The response goes to a program on this computer (http://127.0.0.1, localhost).
resourcestringRequired
What the token will be good for: the MCP server (https://mcp.machinemail.dev/mcp, or the legacy <issuer>/mcp) or <issuer>/ (the REST API and MCP).
scopesScopeDescription[]Required
What the client asked for.
created_atstring (date-time)Required
expires_atstring (date-time)Required
When the request expires (10 minutes after it was made).

client is a OAuthClientInfo:

client_idstringRequired
client_namestring | nullRequired
Self-reported by the client.
client_uristring | nullRequired
Self-reported; https only.
logo_uristring | nullRequired
Self-reported; https only.
registration"dynamic" | "metadata_document"Required
dynamic: registered at /oauth2/register (name and logo unverified). metadata_document: client_id is the URL of its metadata document, so its host is verified.
hoststring | nullRequired
metadata_document: the host that serves the document (for example claude.ai).

Each item of scopes is a ScopeDescription:

scope"inboxes.read" | "inboxes.write" | "messages.read" | "messages.send" | "messages.write" | "webhooks.read" | "webhooks.write" | "domains.read" | "domains.write" | "lists.read" | "lists.write" | "metrics.read" | "pods.read" | "pods.write" | "api_keys.read" | "api_keys.write"Required
OAuth scope. Each grants API key permissions: inboxes.* (inbox_*), messages.read (message, thread and label reads), messages.send (send, reply, drafts), messages.write (update and delete messages and threads), webhooks.*, domains.*, lists.* (allow/block lists and suppressions), pods.*, api_keys.*, metrics.read (metrics and the event log).
descriptionstringRequired

Errors

StatusMeaning
400Invalid request: error.type is validation and error.param names the field.
401Missing, invalid, expired or revoked credential.
500Unexpected server error.

Error bodies use the error envelope.

Example

Request
curl "https://api.machinemail.dev/auth/oauth/authorize?request_id=string" \
  -H "Cookie: mm_session=$SESSION"
Response 200
{
  "request_id": "string",
  "client": {
    "client_id": "my-client-id",
    "client_name": "string",
    "client_uri": "https://example.com",
    "logo_uri": "https://example.com",
    "registration": "dynamic",
    "host": "string"
  },
  "redirect_uri": "https://example.com",
  "redirect_host": "string",
  "redirect_loopback": false,
  "resource": "string",
  "scopes": [
    {
      "scope": "inboxes.read",
      "description": "string"
    }
  ],
  "created_at": "2026-10-01T12:00:00.000Z",
  "expires_at": "2026-10-01T12:00:00.000Z"
}

Approve App Authorization

http
POST /auth/oauth/authorize/approve

Approves a pending request for the signed-in person’s organization, optionally limited to one pod or one inbox, and returns redirect_to (the client’s redirect_uri with code, state and iss) for the browser to follow. The app then appears under Connected apps (/v0/oauth-grants). Dashboard session only.

Authentication: the dashboard mm_session cookie.

Request body

Content type: application/json.

request_idstringRequired
pod_idstring
Limit the grant to one pod of the organization. Default: the whole organization.
inbox_idstring
Limit the grant to one inbox (and its pod).

Response

200 Approved (application/json)

status"approved" | "denied"Required
redirect_tostringRequired
Send the browser here: redirect_uri with code (or error=access_denied), state and iss.

Errors

StatusMeaning
400Invalid request: error.type is validation and error.param names the field.
401Missing, invalid, expired or revoked credential.
500Unexpected server error.

Error bodies use the error envelope.

Example

Request
curl -X POST "https://api.machinemail.dev/auth/oauth/authorize/approve" \
  -H "Cookie: mm_session=$SESSION" \
  -H "Content-Type: application/json" \
  -d '{
  "request_id": "string"
}'
Response 200
{
  "status": "approved",
  "redirect_to": "string"
}

Deny App Authorization

http
POST /auth/oauth/authorize/deny

Denies a pending request and returns redirect_to (the client’s redirect_uri with error=access_denied, state and iss). Dashboard session only.

Authentication: the dashboard mm_session cookie.

Request body

Content type: application/json.

request_idstringRequired

Response

200 Denied (application/json)

status"approved" | "denied"Required
redirect_tostringRequired
Send the browser here: redirect_uri with code (or error=access_denied), state and iss.

Errors

StatusMeaning
400Invalid request: error.type is validation and error.param names the field.
401Missing, invalid, expired or revoked credential.
500Unexpected server error.

Error bodies use the error envelope.

Example

Request
curl -X POST "https://api.machinemail.dev/auth/oauth/authorize/deny" \
  -H "Cookie: mm_session=$SESSION" \
  -H "Content-Type: application/json" \
  -d '{
  "request_id": "string"
}'
Response 200
{
  "status": "approved",
  "redirect_to": "string"
}

List My Organizations

http
GET /auth/organizations

Every organization the signed-in person belongs to, with their role, for the organization switcher.

Authentication: the dashboard mm_session cookie.

Response

200 Organizations (application/json)

organizationsMembershipOrganization[]Required
Every organization the person belongs to, by name.

Each item of organizations is a MembershipOrganization:

organization_idstringRequired
ID of the organization.
namestringRequired
Name of the organization.
slugstringRequired
URL-safe name of the organization.
role"owner" | "admin" | "member"Required
Role in the organization. Owners and admins invite, change roles and remove members; only owners grant or remove the owner role, and an organization always keeps one owner.
currentbooleanRequired
The session is signed in to this organization.

Errors

StatusMeaning
401Missing, invalid, expired or revoked credential.
500Unexpected server error.

Error bodies use the error envelope.

Example

Request
curl "https://api.machinemail.dev/auth/organizations" \
  -H "Cookie: mm_session=$SESSION"
Response 200
{
  "organizations": [
    {
      "organization_id": "org_01k6x2m9qd0000a7h3kq9v2c4e",
      "name": "Production",
      "slug": "string",
      "role": "owner",
      "current": false
    }
  ]
}

Create Organization

http
POST /auth/organizations

Creates an organization (with its Default Pod) owned by the signed-in person, and switches the session to it.

Authentication: the dashboard mm_session cookie.

Request body

Content type: application/json.

namestringRequired
Name of the new organization.

Response

201 Created; the session is on the new organization (application/json)

userUserRequired
A person who signs in to the dashboard.
organizationOrganizationRequired
The organization of the credential. There are no inbox or domain quotas to report (inbox_limit, domain_limit) and no billing fields.

user is a User:

user_idstringRequired
ID of the user.
emailstringRequired
Email address the user signs in with.
namestring | null
Display name, when set.
organization_idstringRequired
ID of the organization this record is about: the session’s active organization, or the organization listed.
role"owner" | "admin" | "member"Required
Role in the organization. Owners and admins invite, change roles and remove members; only owners grant or remove the owner role, and an organization always keeps one owner.
created_atstring (date-time)Required
When the user was created.

organization is a Organization:

organization_idstringRequired
ID of the organization.
namestringRequired
Name of the organization.
slugstringRequired
URL-safe name of the organization.
plan"free" | "pro" | "enterprise"Required
Plan of the organization.
inbox_countintegerRequired
Number of inboxes in the credential scope.
domain_countintegerRequired
Number of domains in the organization.
usageobject
Usage. Always present on GET /v0/organizations and PATCH /v0/organizations.
shared_domainstring
Domain every organization can create inboxes on, used when an inbox is created without domain. Always present on GET /v0/organizations and PATCH /v0/organizations.
updated_atstring (date-time)Required
Time at which the organization was last updated.
created_atstring (date-time)Required
Time at which the organization was created.

Errors

StatusMeaning
400Invalid request: error.type is validation and error.param names the field.
401Missing, invalid, expired or revoked credential.
500Unexpected server error.

Error bodies use the error envelope.

Example

Request
curl -X POST "https://api.machinemail.dev/auth/organizations" \
  -H "Cookie: mm_session=$SESSION" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Production"
}'
Response 201
{
  "user": {
    "user_id": "usr_01k6x3j0rw0000n8w4yc0g3q5s",
    "email": "agent@machinemail.dev",
    "name": "Production",
    "organization_id": "org_01k6x2m9qd0000a7h3kq9v2c4e",
    "role": "owner",
    "created_at": "2026-10-01T12:00:00.000Z"
  },
  "organization": {
    "organization_id": "org_01k6x2m9qd0000a7h3kq9v2c4e",
    "name": "Production",
    "slug": "string",
    "plan": "free",
    "inbox_count": 1,
    "domain_count": 1,
    "usage": {
      "message_count": 1,
      "storage_bytes": 2048,
      "messages_received_30d": 1,
      "messages_sent_30d": 1
    },
    "shared_domain": "string",
    "updated_at": "2026-10-01T12:00:00.000Z",
    "created_at": "2026-10-01T12:00:00.000Z"
  }
}

Switch Organization

http
POST /auth/organizations/switch

Moves the session to another organization the person belongs to. 404 when they do not belong to it. An organization other than the one the session signed in to must allow how the session signed in, under its own sign-in policy: 403 when it does not (for example the organization requires single sign-on and the session signed in with an email code); sign in again to that organization.

Authentication: the dashboard mm_session cookie.

Request body

Content type: application/json.

organization_idstringRequired
ID of an organization the person belongs to.

Response

200 The session, on the organization (application/json)

userUserRequired
A person who signs in to the dashboard.
organizationOrganizationRequired
The organization of the credential. There are no inbox or domain quotas to report (inbox_limit, domain_limit) and no billing fields.

user is a User:

user_idstringRequired
ID of the user.
emailstringRequired
Email address the user signs in with.
namestring | null
Display name, when set.
organization_idstringRequired
ID of the organization this record is about: the session’s active organization, or the organization listed.
role"owner" | "admin" | "member"Required
Role in the organization. Owners and admins invite, change roles and remove members; only owners grant or remove the owner role, and an organization always keeps one owner.
created_atstring (date-time)Required
When the user was created.

organization is a Organization:

organization_idstringRequired
ID of the organization.
namestringRequired
Name of the organization.
slugstringRequired
URL-safe name of the organization.
plan"free" | "pro" | "enterprise"Required
Plan of the organization.
inbox_countintegerRequired
Number of inboxes in the credential scope.
domain_countintegerRequired
Number of domains in the organization.
usageobject
Usage. Always present on GET /v0/organizations and PATCH /v0/organizations.
shared_domainstring
Domain every organization can create inboxes on, used when an inbox is created without domain. Always present on GET /v0/organizations and PATCH /v0/organizations.
updated_atstring (date-time)Required
Time at which the organization was last updated.
created_atstring (date-time)Required
Time at which the organization was created.

Errors

StatusMeaning
400Invalid request: error.type is validation and error.param names the field.
401Missing, invalid, expired or revoked credential.
403The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account_suspended: error.message says what still works; do not retry or re-register).
404Not found. Also returned for resources outside the credential scope.
500Unexpected server error.

Error bodies use the error envelope.

Example

Request
curl -X POST "https://api.machinemail.dev/auth/organizations/switch" \
  -H "Cookie: mm_session=$SESSION" \
  -H "Content-Type: application/json" \
  -d '{
  "organization_id": "org_01k6x2m9qd0000a7h3kq9v2c4e"
}'
Response 200
{
  "user": {
    "user_id": "usr_01k6x3j0rw0000n8w4yc0g3q5s",
    "email": "agent@machinemail.dev",
    "name": "Production",
    "organization_id": "org_01k6x2m9qd0000a7h3kq9v2c4e",
    "role": "owner",
    "created_at": "2026-10-01T12:00:00.000Z"
  },
  "organization": {
    "organization_id": "org_01k6x2m9qd0000a7h3kq9v2c4e",
    "name": "Production",
    "slug": "string",
    "plan": "free",
    "inbox_count": 1,
    "domain_count": 1,
    "usage": {
      "message_count": 1,
      "storage_bytes": 2048,
      "messages_received_30d": 1,
      "messages_sent_30d": 1
    },
    "shared_domain": "string",
    "updated_at": "2026-10-01T12:00:00.000Z",
    "created_at": "2026-10-01T12:00:00.000Z"
  }
}

Get Sign-in in Progress

http
GET /auth/pending

The sign-in in progress in this browser (mm_signin cookie), for the code, organization picker and email verification screens. 404 when there is none or it expired.

Authentication: None.

Response

200 The sign-in in progress (application/json)

step"code" | "organization" | "verify_email"Required
code: waiting for the emailed sign-in code. organization: pick an organization. verify_email: enter the code that verifies the email.
emailstring
The address signing in, when known.
organizationsobject[]
For organization: the organizations to pick from.
invitationbooleanRequired
Signing in accepts an invitation.

Errors

StatusMeaning
404Not found. Also returned for resources outside the credential scope.
500Unexpected server error.

Example

Request
curl "https://api.machinemail.dev/auth/pending"
Response 200
{
  "step": "code",
  "email": "agent@machinemail.dev",
  "organizations": [
    {
      "id": "string",
      "name": "Production"
    }
  ],
  "invitation": false
}

Choose Organization

http
POST /auth/pending/organization

Finishes a sign-in that needs an organization picked (organization_selection_required) with one of the organizations offered (GET /auth/pending); any other ID is 400.

A request whose Origin is another site is refused (403).

Authentication: None.

Request body

Content type: application/json.

organization_idstringRequired
One of the organizations from GET /auth/pending.

Response

200 Signed in, or the next step (application/json)

Headers:

Set-Cookiestring
mm_signin=<handle>; HttpOnly; SameSite=Lax; Path=/auth (the sign-in in progress, 10 minutes), and mm_session once signed in.
oktrueRequired
signed_inbooleanRequired
true: the mm_session cookie is set. false: another step comes first (organization picker, email verification, SSO).
redirect_tostringRequired
Where to send the browser next, built by the server: next (with welcome=1 after a first sign-in), /login/organization, /login/verify, an identity provider, or /login?error=<code>.

Errors

StatusMeaning
400Invalid request: error.type is validation and error.param names the field.
403The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account_suspended: error.message says what still works; do not retry or re-register).
404Not found. Also returned for resources outside the credential scope.
429Rate limited.
500Unexpected server error.

Example

Request
curl -X POST "https://api.machinemail.dev/auth/pending/organization" \
  -H "Content-Type: application/json" \
  -d '{
  "organization_id": "org_01k6x2m9qd0000a7h3kq9v2c4e"
}'
Response 200
{
  "ok": true,
  "signed_in": false,
  "redirect_to": "string"
}

Verify Email

http
POST /auth/pending/verify

Finishes a sign-in that needs the email verified (email_verification_required, for example a GitHub account whose email GitHub does not vouch for) with the code emailed to that address. Wrong codes as for POST /auth/email/verify. Each such sign-in is a new code for the address, counted with email codes (POST /auth/email/start): past those limits the callback goes to /login?error=codes_paused and no code is sent.

Rate limited per client IP and per email address (5 attempts, then 1 per minute, per address and client IP): 429 with Retry-After.

A request whose Origin is another site is refused (403).

Authentication: None.

Request body

Content type: application/json.

codestringRequired
The 6-digit code from the email. Spaces and dashes are ignored.

Response

200 Signed in, or the next step (application/json)

Headers:

Set-Cookiestring
mm_signin=<handle>; HttpOnly; SameSite=Lax; Path=/auth (the sign-in in progress, 10 minutes), and mm_session once signed in.
oktrueRequired
signed_inbooleanRequired
true: the mm_session cookie is set. false: another step comes first (organization picker, email verification, SSO).
redirect_tostringRequired
Where to send the browser next, built by the server: next (with welcome=1 after a first sign-in), /login/organization, /login/verify, an identity provider, or /login?error=<code>.

Errors

StatusMeaning
400Invalid request: error.type is validation and error.param names the field.
403The credential lacks the permission or scope for this request (error.type forbidden), or MachineMail suspended the organization or its sending (account_suspended: error.message says what still works; do not retry or re-register).
404Not found. Also returned for resources outside the credential scope.
429Rate limited.
500Unexpected server error.

Example

Request
curl -X POST "https://api.machinemail.dev/auth/pending/verify" \
  -H "Content-Type: application/json" \
  -d '{
  "code": "string"
}'
Response 200
{
  "ok": true,
  "signed_in": false,
  "redirect_to": "string"
}

Sign Up (Deprecated)

http
POST /auth/signup

Warning

Deprecated.

Deprecated: the dashboard signs in with POST /auth/magic-link. Creates a user, an organization, a Default Pod and a first organization API key (returned once as api_key), and sets the mm_session cookie.

It does not verify the email address, so it is off in production unless the server sets MACHINEMAIL_PASSWORD_AUTH=true: 404 otherwise.

A taken address and a password shorter than 8 characters both answer 409 with the same message.

Rate limited per client IP and per email address (5 attempts, then 1 per minute): 429 with Retry-After.

Authentication: None.

Request body

Content type: application/json.

emailstringRequired
passwordstringRequired
At least 8 characters.
namestring
Display name of the user.
org_namestring
Organization name.
organization_namestring
Alias of org_name.
organizationNamestringDeprecated
Alias of org_name.

Response

201 Signed up and signed in (application/json)

Headers:

Set-Cookiestring
mm_session=<token>; HttpOnly; SameSite=Lax; Path=/ (plus Secure on https).
userUserRequired
A person who signs in to the dashboard.
organizationOrganizationRequired
The organization of the credential. There are no inbox or domain quotas to report (inbox_limit, domain_limit) and no billing fields.
api_keyOneTimeApiKeyRequired
An API key with its plaintext secret, returned once.

user is a User:

user_idstringRequired
ID of the user.
emailstringRequired
Email address the user signs in with.
namestring | null
Display name, when set.
organization_idstringRequired
ID of the organization this record is about: the session’s active organization, or the organization listed.
role"owner" | "admin" | "member"Required
Role in the organization. Owners and admins invite, change roles and remove members; only owners grant or remove the owner role, and an organization always keeps one owner.
created_atstring (date-time)Required
When the user was created.

organization is a Organization:

organization_idstringRequired
ID of the organization.
namestringRequired
Name of the organization.
slugstringRequired
URL-safe name of the organization.
plan"free" | "pro" | "enterprise"Required
Plan of the organization.
inbox_countintegerRequired
Number of inboxes in the credential scope.
domain_countintegerRequired
Number of domains in the organization.
usageobject
Usage. Always present on GET /v0/organizations and PATCH /v0/organizations.
shared_domainstring
Domain every organization can create inboxes on, used when an inbox is created without domain. Always present on GET /v0/organizations and PATCH /v0/organizations.
updated_atstring (date-time)Required
Time at which the organization was last updated.
created_atstring (date-time)Required
Time at which the organization was created.

api_key is a OneTimeApiKey:

api_key_idstringRequired
ID of the API key.
api_keystringRequired
Plaintext secret (mm_live_...). Shown once.
prefixstringRequired
First characters of the key, shown in listings.
namestringRequired
Name of the key.
created_atstring (date-time)Required
When the key was created.

Errors

StatusMeaning
400Invalid request: error.type is validation and error.param names the field.
404Not found. Also returned for resources outside the credential scope.
409Conflicts with an existing resource.
429Rate limited.
500Unexpected server error.

Example

Request
curl -X POST "https://api.machinemail.dev/auth/signup" \
  -H "Content-Type: application/json" \
  -d '{
  "email": "agent@machinemail.dev",
  "password": "string",
  "name": "Production"
}'
Response 201
{
  "user": {
    "user_id": "usr_01k6x3j0rw0000n8w4yc0g3q5s",
    "email": "agent@machinemail.dev",
    "name": "Production",
    "organization_id": "org_01k6x2m9qd0000a7h3kq9v2c4e",
    "role": "owner",
    "created_at": "2026-10-01T12:00:00.000Z"
  },
  "organization": {
    "organization_id": "org_01k6x2m9qd0000a7h3kq9v2c4e",
    "name": "Production",
    "slug": "string",
    "plan": "free",
    "inbox_count": 1,
    "domain_count": 1,
    "usage": {
      "message_count": 1,
      "storage_bytes": 2048,
      "messages_received_30d": 1,
      "messages_sent_30d": 1
    },
    "shared_domain": "string",
    "updated_at": "2026-10-01T12:00:00.000Z",
    "created_at": "2026-10-01T12:00:00.000Z"
  },
  "api_key": {
    "api_key_id": "key_01k6x3e8mr0000h4r0ty6c9k1n",
    "api_key": "mm_live_4f8Kq2ZbX9mN3pR7tV1wY6cH0jL5sD8a",
    "prefix": "mm_live_4f8K",
    "name": "Production",
    "created_at": "2026-10-01T12:00:00.000Z"
  }
}

Sign In with a Social Provider

http
GET /auth/social/{provider}

Sends the browser straight to Google, GitHub, Apple or Microsoft (no page in between), with a single-use state bound to this browser by a short-lived cookie (mm_oauth_state, Path=/auth/callback). The provider returns to GET /auth/callback.

404 when this server does not offer social sign-in or that provider (GET /auth/config lists them).

Authentication: None.

Path parameters

provider"google" | "github" | "apple" | "microsoft"Required
A social sign-in provider.

Query parameters

nextstring
Same-origin path to land on after sign-in. Default /app.
invitation_tokenstring
An invitation token (from /invite/{token}): signing in accepts that invitation.

Response

302 To the provider.

Headers:

Locationstring
Where to go next.

Errors

StatusMeaning
400Invalid request: error.type is validation and error.param names the field.
404Not found. Also returned for resources outside the credential scope.
500Unexpected server error.

Example

Request
curl "https://api.machinemail.dev/auth/social/google"