Organizations and tenancy

Understand how a Samva credential binds to an organization, why every record is tenant-scoped, and how teams and usage share that boundary.

Every request to Samva's email API answers two questions before anything else happens: who is asking, and whose data are they allowed to touch. A credential answers the first. Tenancy answers the second. This page explains how the two meet in the organization. For key formats, headers, permissions, and OAuth sign-in, see the Authentication reference.

The organization is the unit of tenancy

Samva's tenant is the organization. An organization owns everything: verified domains, senders, contacts, conversations, messages, templates, webhooks, and the API keys themselves. When you sign up, you get an organization; when you invite teammates, they join your organization; when you send an email, it is recorded against your organization.

Every record belongs to one organization, and every read is limited to it. There is no request shape that crosses the boundary. A key issued to one organization cannot list another organization's contacts, fetch its messages, or send from its domain. The organization comes from your credential rather than from anything in the request body, so there is no field in which to ask for the wrong tenant's data.

A verified domain is an organization-level asset for the same reason. Authenticating a sending domain, covered in Verify your domain, is done once per organization, and from then on every key in that organization can send from it. The trust you build attaches to the tenant, not to an individual credential.

The organization avatar

An organization has one avatar. It is the image the organization switcher shows next to the organization's name, and a generated glyph stands in until you set one. Owners and admins set it in the dashboard under Settings → Organization → Basic information: choose Upload avatar, frame the square crop, and save. Remove returns the switcher to the glyph. Samva stores a square copy of the image you upload. The upload target below reports the size limit as maxBytes and the accepted types as contentTypes.

The avatar belongs to the organization itself. The logo and icon your emails carry belong to a brand, and changing one never changes the other.

From the CLI, samva org avatar set <file> uploads a PNG, JPEG, or WebP image and saves it, and samva org avatar clear removes it:

samva org avatar set avatar.png

A key with permission to update the organization sets it through the API in three calls. The first returns where to upload, the second sends the image bytes there, and the third saves the address that comes back:

Set the organization avatar with the SDK
import { readFile } from "node:fs/promises";
import { createClient } from "samva";

const apiKey = process.env.SAMVA_API_KEY!;
const samva = createClient({ apiKey });

const { uploadUrl } = await samva.organizations.createAvatarUpload();
const upload = await fetch(uploadUrl, {
  method: "PUT",
  headers: { "X-API-Key": apiKey, "Content-Type": "image/png" },
  body: await readFile("avatar.png"),
});
const { src } = (await upload.json()) as { src: string };

await samva.organizations.update({ avatarUrl: src });

The same three calls with curl:

Set the organization avatar with curl
UPLOAD_URL=$(curl -s -X POST https://api.samva.dev/v1/organizations/current/avatar-upload \
  -H "X-API-Key: $SAMVA_API_KEY" | jq -r .uploadUrl)

SRC=$(curl -s -X PUT "$UPLOAD_URL" \
  -H "X-API-Key: $SAMVA_API_KEY" \
  -H "Content-Type: image/png" \
  --data-binary @avatar.png | jq -r .src)

curl -X PATCH https://api.samva.dev/v1/organizations/current \
  -H "X-API-Key: $SAMVA_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"avatarUrl\": \"$SRC\"}"

PATCH /v1/organizations/current accepts only an address returned by an avatar upload, and answers 422 with a ValidationError on avatarUrl for any other value. Send { "avatarUrl": null } to remove the avatar. See Get the organization avatar upload URL and Update the current organization for the full shapes.

How a credential finds its organization

Samva's credentials differ in who is acting, and that decides how the organization is chosen.

An API key belongs to exactly one organization. Presenting it tells Samva both that the request is legitimate and whose data it may read or write, so key-authenticated calls never name an organization. That is what makes a key the right credential for a server: nothing about the request can point it somewhere else.

A CLI OAuth session belongs to your user, and a user can be a member of several organizations. The session therefore names its organization on every request, and Samva checks your membership before acting. Switching organizations is a local choice in the CLI, not a new login.

An MCP OAuth token belongs to your user but is bound, when you approve the connection, to one organization. An agent holding it cannot switch organizations, which keeps a connected agent inside the tenant you granted.

Teams and roles

Because the organization owns the data and the credentials, it is also where collaboration lives. You invite teammates into an organization by email address and assign each one a role. Each person then authenticates as themselves rather than borrowing anyone else's credential.

Roles exist for least privilege: a teammate who only needs to read delivery activity should not also be able to rotate the API keys production depends on. The same instinct applies to keys: a key never holds more than its owner's current role, and a restricted key holds only the resources you grant it. Roles and their capabilities are managed in the dashboard.

Usage is organization-scoped

Usage follows the same boundary. Every send your organization makes counts against the organization's allowance, not against the key that made it, so adding keys neither adds capacity nor fragments spend. When an organization reaches a limit, the API refuses the request rather than silently sending; Sending limits states the limits.

Where to go next

Related resources

Related documentation

On this page