Concepts

Authentication

Every request authenticates with an API key — or, for third-party integrations, an OAuth access token — and the key's scopes decide exactly what it may do. This page covers the key format, the scope model, rotation, and where OAuth fits.

API keys

Keys are created under Settings → API keys (Professional plan or higher). The secret is shown once, at creation, and stored hashed — it cannot be shown again. A token looks like:

mk_live_<keyId>_<secret><checksum>
  • mk_live_ for live keys, mk_test_ for test keys. A whole token is 68 characters.
  • The last 6 characters are a checksum, verified before any database lookup — a truncated paste fails fast with api_key_malformed rather than a vague 401.
  • Only mk_live_<keyId> is ever displayed back to you. It contains no character of the secret.
  • Keep keys server-side. Anything placed in a browser, a mobile app or a public repository is compromised.
curl "https://api.mailneo.co/api/v2/me" \
  -H "x-api-key: $MAILNEO_API_KEY"

Two equivalent headers

Send the key either way — pick whichever your HTTP client makes easy:

  • X-API-Key: mk_live_...
  • Authorization: Bearer mk_live_...

Sending both is fine only if they are identical. Two different credentials in one request is never legitimate, so it is refused with api_key_invalid rather than resolved to one of them.

Live and test environments

A test token presented against a live key record — or the reverse — is api_key_environment_mismatch, never a fallback. A test secret that leaks cannot be replayed against live data, and test keys can never send mail.

The scope model

A scope is resource:tier. Every endpoint declares exactly one scope, and the check runs before the endpoint does: a key missing it gets insufficient_scope, and the message names the scope it needed — scopes are public documentation, so saying which one costs nothing and saves a support ticket.

The four tiers

  • read — list and fetch. Where a resource supports it, this includes incremental sync with updated_since.
  • write — create and mutate. On campaigns and newsletters, writes are drafts-only: send-shaped fields (status, send_at, drip flags) are refused by name, never silently ignored.
  • delete — a separate tier where deletion is a materially bigger power than editing (contacts, subscribers, suppressions). A key can hold write without delete.
  • send — arm real mail delivery, through the two-step preview/confirm flow. Send keys carry extra controls: a send_domains allowlist (empty means the key cannot send at all) and a daily send cap. OAuth clients can never request this tier.

Scopes on the published surface

Resourcereadwritedeletesend
accounts
analytics
campaigns
contacts
lists
newsletters
segments
subscribers
suppressions
templates
webhooks

Grant the narrowest set that does the job, and widen when a call answers insufficient_scope. There is deliberately no scope for the unified inbox and no scope that writes email-account credentials — a permission that does not exist cannot be mis-granted. GET /me and GET /usage are scope-free so every integration can identify itself and self-throttle.

Keys follow their creator

A key's permissions are its creator's, re-resolved continuously rather than frozen at creation. If that person's role is narrowed or they leave the team, the key narrows or stops working with role_insufficient — a role change takes effect within a minute. GET /me reports both granted_scopes (what was granted at mint) and scopes (what is effective right now).

Rotation and revocation

  • Rotate(Settings → API keys) when a secret is lost or may have leaked: rotation issues a fresh secret and the old one stops authenticating. Rotation preserves the key's lineage — the daily send cap is counted across the whole rotation lineage, so rotating a key never resets its cap.
  • Revoke to end a key outright. Revocation is immediate (api_key_revoked) and is not reversible.

Every way authentication can fail has its own stable code — see errors.

API versioning

  • A dated version is pinned to the key at creation — today's keys pin 2026-08-15 — and never moves on its own.
  • Send Mailneo-Version to override it for one request; the resolved version comes back in the same header on every authenticated response.
  • An unrecognised value is version_unsupported, never a silent fallback — a client that asked for a version it did not get would misparse every response.

OAuth for integrations

Third-party apps and AI agents do not handle raw API keys. Mailneo runs an OAuth 2.1 authorization server — discovery, dynamic client registration and PKCE — and an access token is presented as Authorization: Bearer on the same endpoints. A bad or expired token answers invalid_token (RFC 6750's name), with a WWW-Authenticate challenge pointing back at the authorization server.

OAuth clients request scopes on the consent screen, and the send tier is not requestable at all — an agent connected over OAuth can draft and preview, never arm a send. The practical setup guide (Claude connector, MCP) lives at Resources → MCP.