Authentication
Every request authenticates with a bearer token in the
Authorization header. Your account key — one
mk_live_… token — does everything: sending, domains, webhooks, and
inbound. When code or an agent should reach less, mint a
domain-scoped key confined to a single domain.
Account key — full scope
Your account has a single mk_live_… account key from the moment you
sign up. It's scoped to all your domains and authenticates every
endpoint: POST /v1/send and the whole management API
(/api/*) — create & register domains, verify DNS, set webhooks,
create routes, and read inbound. The same key works for your own code and for an
AI agent, so there's nothing extra to provision.
Domain-scoped keys — one domain each
A domain-scoped key is also an mk_live_… token, but it can only
touch one domain: send from it, manage it, and read its mail
and routes — nothing else on the account, and it can't mint keys or change
account-wide settings. Create one in the dashboard under
API keys (pick the domain, give it a label), and revoke it
anytime without disturbing the account key or other scoped keys. Use one per
agent or per tenant in production —
choosing an agent's reach walks through the
full ladder, down to a single mailbox.
| Credential | Looks like | Use it for |
|---|---|---|
| Account key | mk_live_… | Everything — sending and the full management API, from your server or your agent. |
| Domain-scoped key | mk_live_… | One domain only — send, manage, and read mail there. One per agent or tenant; revocable on its own. |
| Session token | JWT (eyJ…) | The dashboard's own browser session (email/password or Google sign-in). You rarely handle this directly. |
Using your API key
Grab your key from the dashboard (it's on your first screen
and under Settings → API key) and pass it to the client — it goes out as
Authorization: Bearer mk_live_… on every request:
import { MailKite } from "mailkite";
const mk = new MailKite(process.env.MAILKITE_API_KEY);import os
from mailkite import MailKite
mk = MailKite(os.environ["MAILKITE_API_KEY"])<?php
$mk = new \MailKite\Client(getenv('MAILKITE_API_KEY'));import dev.mailkite.MailKite;
MailKite mk = new MailKite(System.getenv("MAILKITE_API_KEY"));mk := mailkite.New(os.Getenv("MAILKITE_API_KEY"))require "mailkite"
mk = Mailkite::Client.new(ENV["MAILKITE_API_KEY"])# pass -H "Authorization: Bearer $MAILKITE_API_KEY" on each request Treat it like a password — store it in a secret manager or environment variable, never in client-side code or a git repo, and use it server-side only. Need a fresh one? Rotate the account key in the dashboard (rotating invalidates the old key immediately), or revoke any domain-scoped key on its own.
Session tokens (dashboard)
The dashboard signs in with email + password (or
Sign in with Google) to get a short-lived JWT session
token. You can use it against the management API too, but for scripts and agents
prefer the mk_live_… key above. To get a token:
curl https://api.mailkite.dev/api/auth/login \
-H "Content-Type: application/json" \
-d '{ "email": "you@example.com", "password": "••••••••" }' {
"token": "eyJhbGci…",
"user": { "id": "usr_…", "email": "you@example.com", "isAdmin": false }
} Send the token on subsequent management requests:
const mk = new MailKite("eyJhbGci…");
const domains = await mk.listDomains();mk = MailKite("eyJhbGci…")
domains = mk.listDomains()$mk = new \MailKite\Client('eyJhbGci…');
$domains = $mk->listDomains();MailKite mk = new MailKite("eyJhbGci…");
Object domains = mk.listDomains();mk := mailkite.New("eyJhbGci…")
domains, err := mk.ListDomains()mk = Mailkite::Client.new("eyJhbGci…")
domains = mk.listDomainscurl https://api.mailkite.dev/api/domains \
-H "Authorization: Bearer eyJhbGci…"
To create an account programmatically, POST /api/auth/signup
with { email, password } — it returns a token too.
Errors
A missing or invalid credential returns 401 Unauthorized. A valid
credential that lacks access to a resource returns 403 Forbidden,
and an unknown or non-owned resource returns 404 Not Found. See
the API reference for the full list.
All requests must use HTTPS. Requests over plain HTTP are rejected (except
http://localhost for local webhook testing).