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

CredentialForHeaderOrganization
API keyServers, backends, CI, agentsX-API-KeyBound to the key's organization
CLI OAuth sessionA person at a terminalAuthorization: Bearer <token>Chosen per request with x-org-slug
MCP OAuth tokenAn MCP client you connectedAuthorization: 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 read or write. read grants the resource's read action; write grants 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.

create-restricted-key.ts
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.

ResourceActionsCovers
messagescreate, read, update, deleteSends, scheduled sends, campaigns, media, and attachments
emailsend, readEmail sends, blocks, suppressions, tracking preferences, statistics, and email review
domainscreate, read, update, deleteSending domains, senders, custom tracking, catch-all, forwarding, and domain warming
contactscreate, read, update, deleteContacts, contact groups, custom fields, and unsubscribe groups
conversationscreate, read, update, deleteConversations and their contacts
templatescreate, read, update, deleteTemplates, template families, workspaces, and publications
webhookscreate, read, update, deleteWebhook endpoints, deliveries, and signing secrets
apiKeyscreate, read, update, delete, rotateAPI keys and their usage
organizationread, updateThe organization record and its usage
billingreadThe organization's plan and usage balances
analyticsreadAggregate 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:

  1. The CLI requests a device code and shows a verification URL and a short user code.
  2. It opens your browser, or prints the URL with --no-browser.
  3. You approve the session in the browser, signed in as your Samva user.
  4. 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.

Related documentation

On this page