Authentication
Reference Samva's credentials, covering API key format and headers, permissions, OAuth sessions for the CLI and MCP, and authentication errors.
Every request to the Samva API carries one credential, and the credential decides which organization the request acts on. This page is the reference for each credential type. For the model behind it, how an organization bounds every record and why a key belongs to exactly one, see Organizations and tenancy.
Credential types
| Credential | For | Header | Organization |
|---|---|---|---|
| API key | Servers, backends, CI, agents | X-API-Key | Bound to the key's organization |
| CLI OAuth session | A person at a terminal | Authorization: Bearer <token> | Chosen per request with x-org-slug |
| MCP OAuth token | An MCP client you connected | Authorization: Bearer <jwt> | Bound to the organization chosen at consent |
Use an API key for anything unattended. Use an OAuth session for interactive work, especially across several organizations, when you do not want a long-lived key on your machine.
API keys
Key format
Production keys start with samva_sk_live_; keys minted outside production start with
samva_sk_test_. Each key authenticates requests for the organization it belongs to, so a
key-authenticated request never names an organization.
Send the key
Send the key in the X-API-Key header on every request.
The SDK reads the key from the apiKey option and sets the header for you.
import { createClient } from "samva";
const samva = createClient({ apiKey: process.env.SAMVA_API_KEY! });The hosted MCP server also accepts an API key as Authorization: Bearer samva_sk_live_…. The REST
API reads a key only from X-API-Key.
Create, rotate, and revoke keys
Create keys under Developers → API Keys in the Samva Dashboard,
or with samva.apiKeys.create. Each key carries its access and an optional expiry, both set at
creation. The full key value is returned only once, at creation; a lost key must be replaced.
From the CLI, samva api-keys create --name ci --grant email=write creates a restricted key and
samva api-keys create --name ci --full-access a full-access one; --json returns the key as
data.key for scripts. See Manage API keys.
Rotating a key issues a replacement with the same name, owner, and access and deactivates the old
one.
Revoking a key (POST /v1/api-keys/{id}/revoke) stops it authenticating immediately; the key
stays listed as inactive, so the messages it sent keep their attribution. Store keys in environment variables, never in
source, and rotate a key after any suspected exposure.
Access
A key belongs to the member who created it and never holds more than that member's current role. Choose one of two kinds when you create it:
- Full access holds everything its owner's role can do. It follows the role: a demoted owner's key loses what the role lost, and a resource added to the role later is included.
- Restricted holds only the resources you pick, each at
readorwrite.readgrants the resource'sreadaction;writegrants every action listed for it below. The owner's current role still caps it on every request.
No key can delete the organization, change billing, or manage members; those stay with a signed-in owner. A key whose owner leaves the organization stops authenticating. A restricted key that creates another key can grant only what it holds, and cannot create a full-access key.
A restricted key can also hold mailboxes access, which reaches only the mailboxes you list. See
Agents, people, and keys.
import { createClient } from "samva";
const samva = createClient({ apiKey: process.env.SAMVA_API_KEY! });
const key = await samva.apiKeys.create({
name: "Transactional sender",
access: { mode: "restricted", resources: { messages: "write", templates: "read" } },
});Every endpoint declares the resource:action permission it requires, and a key whose grants do not
cover it is refused with 403 ForbiddenError. The published OpenAPI document states the
requirement on each operation as x-samva-scope.
| Resource | Actions | Covers |
|---|---|---|
messages | create, read, update, delete | Sends, scheduled sends, campaigns, media, and attachments |
email | send, read | Email sends, blocks, suppressions, tracking preferences, statistics, and email review |
domains | create, read, update, delete | Sending domains, senders, custom tracking, catch-all, forwarding, and domain warming |
contacts | create, read, update, delete | Contacts, contact groups, custom fields, and unsubscribe groups |
conversations | create, read, update, delete | Conversations and their contacts |
templates | create, read, update, delete | Templates, template families, workspaces, and publications |
webhooks | create, read, update, delete | Webhook endpoints, deliveries, and signing secrets |
apiKeys | create, read, update, delete, rotate | API keys and their usage |
organization | read, update | The organization record and its usage |
billing | read | The organization's plan and usage balances |
analytics | read | Aggregate messaging statistics and exports |
Sending and provisioning are separate. email is traffic: it sends mail and reads what happened to
it. What the mail is sent from is domains: your sending domains and senders, their DNS, custom
tracking, catch-all, and forwarding. A key scoped to send cannot delete the domain it sends from.
Two operations have no scope. POST /v1/feedback reports to Samva and touches nothing your
organization owns, so any key your organization issued can send feedback. GET /v1/whoami returns
the organization and credential behind the request, so any valid credential can learn who it is,
whatever it may touch:
curl https://api.samva.dev/v1/whoami -H "X-API-Key: $SAMVA_API_KEY"For an API key the response carries organization (id, slug, name) and a credential of
type: "apiKey" with the key's id, name, start, and access. For a signed-in person or an
OAuth token, credential is type: "user" with the user and their role. samva org current
uses it to name the organization of SAMVA_API_KEY.
The action follows the method unless the endpoint says otherwise: GET reads, POST creates,
PATCH and PUT update, DELETE deletes. A resource without that action uses its nearest one, so
an email mutation such as adding a block requires email:send. An endpoint that reads through a
POST body, such as POST /v1/contacts/find, requires the read action. An endpoint whose method
overstates it says so: POST /v1/email/domains/{id}/default requires domains:update.
A few endpoints require more than one permission, published space-separated.
POST /v1/contacts/bulk-import requires contacts:create contacts:update, because it overwrites
contacts that already exist.
Send permissions
An email send needs messages:create or email:send, whether it arrives through
POST /v1/messages, a scheduled send, or SMTP. A key holding only email:send can send email but
cannot create or run a campaign, which needs messages:create. Because the requirement depends on
the request body, the OpenAPI document publishes it on the send endpoints as alternatives joined by
or.
OAuth sessions
Sign in from the CLI
samva login runs the OAuth 2.0 device authorization flow:
- The CLI requests a device code and shows a verification URL and a short user code.
- It opens your browser, or prints the URL with
--no-browser. - You approve the session in the browser, signed in as your Samva user.
- The CLI stores the session token in your operating system's credential store.
The CLI never writes the token to a file. If the credential store cannot save it, samva login
fails. Requests then send Authorization: Bearer <token> instead of X-API-Key. See
Use the CLI for how the CLI resolves the
credential and organization.
Choose the organization
A CLI session belongs to your user, not to one organization, so each request names the
organization in the x-org-slug header. The CLI sends the organization you select with
samva org use <slug>. With the SDK, pass it when you construct a client with authToken:
import { createClient } from "samva";
const samva = createClient({
authToken: process.env.SAMVA_AUTH_TOKEN!,
headers: { "x-org-slug": "acme" },
});A request authenticated with an API key or an MCP OAuth token ignores x-org-slug; the organization
comes from the credential.
Session lifetime
A CLI session has no refresh token. When it expires, the CLI reports an authentication failure;
run samva login again.
MCP OAuth tokens
An MCP client that connects with OAuth receives a token bound to the one organization you choose when you approve the connection. See Connect an agent over MCP.
Authentication errors
A missing, invalid, revoked, or expired credential returns 401 UnauthorizedError. A valid
credential that lacks the endpoint's permission returns 403 ForbiddenError, and its message
names the permission the request needed, for example Caller requires contacts:create permission.
A CLI session request without x-org-slug, or naming an organization you do not belong to, also
returns 403 ForbiddenError. Every error, with its fields and how to resolve it, is in the
Error reference.