> Source: https://builder.ema.ai/v2/api-reference/authentication
> Title: Authentication

# Authentication

Every Ema API call must carry a credential that identifies the caller and fixes their tenant. The platform supports two: a short-lived **Bearer JWT** for user-driven access (what the builder UI uses), and a long-lived **tenant API key** for programmatic, machine-to-machine access. This page explains how to obtain each and how to send it.

> [INFO]
> **Pick one per request.** A request must carry exactly one credential. If the `Authorization` header is present, the platform validates it as a JWT and will not fall back to an API key. If there is no `Authorization` header, it validates the `X-API-Key` header. With neither, the call returns `401 Unauthorized`.

## JWT access tokens

A JWT (JSON Web Token) is a short-lived access token tied to a specific user, tenant, and role. It is the right credential for anything that acts on behalf of a signed-in person.

### Sign in with email and password

`POST /api/v1/auth/login` exchanges credentials for tokens. This route is public — it needs no prior credential.

```http
POST https://your-tenant.ema.co/api/v1/auth/login
Content-Type: application/json

{
  "email": "builder@acme.com",
  "password": "••••••••",
  "tenant": "acme"
}
```

The `tenant` field is optional. Omit it for email-first sign-in (the server routes you to your home tenant); include the tenant slug when signing in through a tenant-pinned URL.

A successful response returns the tokens and the resolved user:

```json
{
  "access_token": "eyJhbGciOiJSUzI1Ni...",
  "refresh_token": "eyJhbGciOiJSUzI1Ni...",
  "user": {
    "id": "8c1f...",
    "tenant_id": "a4d2...",
    "email": "builder@acme.com",
    "name": "Avery Builder",
    "role": "builder",
    "status": "active"
  }
}
```

> [TIP]
> **The builder UI uses cookies, not headers.** When you sign in through the browser, the gateway stores `access_token` and `refresh_token` as HttpOnly cookies and promotes the access token to an `Authorization: Bearer` header on every backend call for you. The JSON body above is what programmatic clients use; the cookie flow is for the SPA. Either way the underlying token is the same.

### Send the token

Put the access token in the `Authorization` header on every subsequent call:

```http
GET https://your-tenant.ema.co/api/v1/workflow/workflows
Authorization: Bearer eyJhbGciOiJSUzI1Ni...
```

### Refresh an expired token

Access tokens are short-lived. When one expires, exchange the refresh token for a new access token at `POST /api/v1/auth/refresh`:

```http
POST https://your-tenant.ema.co/api/v1/auth/refresh
Content-Type: application/json

{ "refresh_token": "eyJhbGciOiJSUzI1Ni..." }
```

```json
{ "access_token": "eyJhbGciOiJSUzI1Ni..." }
```

Refresh tokens are long-lived and are **not** rotated on refresh — keep reusing the one you received at sign-in until it expires. The endpoint returns only a new access token; there is no rolling-refresh scheme.

### Other sign-in methods

Beyond email and password, the auth service supports:

-   **SSO (SAML and OIDC).** Sign-in begins at `POST /api/v1/auth/begin`, which returns the method (`sso`, `password`, or `unknown`) for an email. The SSO branch returns an opaque continuation token you redeem at `GET /api/v1/auth/sso/continue`, which redirects into the configured identity provider. The callback returns the same token shape as login.
-   **One-time passcode (OTP).** `POST /api/v1/auth/otp/request` emails a code; `POST /api/v1/auth/otp/verify` exchanges it for tokens (or a partial-auth token when TOTP is also required).
-   **TOTP (two-factor).** When a tenant requires it, OTP/SSO sign-in returns a `partial_auth_token`; complete the second factor at `POST /api/v1/auth/totp/verify`.

These are browser-driven flows; the email-and-password and refresh endpoints above are what most programmatic clients need.

### Switch tenants

If your user belongs to more than one tenant, list them with `GET /api/v1/auth/users/me/tenants` and mint tokens for a different one with `POST /api/v1/auth/switch-tenant`:

```http
POST https://your-tenant.ema.co/api/v1/auth/switch-tenant
Authorization: Bearer eyJhbGciOiJSUzI1Ni...
Content-Type: application/json

{ "target_tenant_id": "b7e9..." }
```

The response is a fresh `LoginResponse` (new tokens) scoped to the target tenant.

## Tenant API keys

A tenant API key is a long-lived credential for programmatic access — scripts, backend services, and partner integrations that aren't tied to an interactive user session. It is the right credential for server-to-server automation.

### Create a key

An administrator creates a key at `POST /api/v1/auth/api-keys`. This requires a JWT (you must be signed in to mint a key).

```http
POST https://your-tenant.ema.co/api/v1/auth/api-keys
Authorization: Bearer eyJhbGciOiJSUzI1Ni...
Content-Type: application/json

{
  "name": "Nightly export job",
  "key_type": "user"
}
```

The response includes the full key **once** — store it securely, because it is never shown again:

```json
{
  "id": "f0a1...",
  "name": "Nightly export job",
  "key": "ema_sk_live_2f9c...full-secret",
  "key_prefix": "ema_sk_live_2f9c",
  "key_type": "user",
  "created_at": "2026-06-01T12:00:00Z"
}
```

`key_type` is `user` (the default — a key owned by and acting as the creating user) or `system` (a tenant-level service key; creating one requires admin role). You may also pass `operation_scope` and `resource_scope` to narrow what the key can do — for example, restricting a key to a single AI Employee.

### Send the key

Put the key in the `X-API-Key` header, with no `Authorization` header on the request:

```http
GET https://your-tenant.ema.co/api/v1/workflow/workflows
X-API-Key: ema_sk_live_2f9c...full-secret
```

The key carries its own tenant and owner, so calls made with it behave as that owner within that tenant.

### Manage keys

-   **List** the tenant's keys: `GET /api/v1/auth/api-keys`. The response shows each key's `key_prefix` (never the full secret), type, owner, and revocation state.
-   **Update scope** on a key: `PATCH /api/v1/auth/api-keys/{id}` with a new `operation_scope` / `resource_scope`.
-   **Revoke** a key: `DELETE /api/v1/auth/api-keys/{id}`. Revocation is immediate.

> [WARNING]
> **Treat keys like passwords.** A tenant API key grants its owner's access to your tenant for as long as it lives. Never commit one to source control or embed it in client-side code. Rotate keys by creating a replacement and revoking the old one.

## Roles and capabilities

Authenticating proves who you are; your **role** determines what you may do. The system roles, from most to least privileged, are `env_admin`, `system_admin`, `builder_admin`, `user_admin`, `builder`, `user`, and `no_access`. Building and publishing AI Employees generally requires `builder`, `builder_admin`, or `system_admin`. When a request is authenticated but the role lacks the required capability, the platform returns `403 Forbidden`. When it lacks visibility of a resource, it returns `404` so existence is never leaked across tenants.

## What's next

-   [API Reference overview](/builder/v2/api-reference) — base URL and the service prefix map.
-   [AI Employee API](/builder/v2/api-reference/ai-employee-api) — build and publish AI Employees once you can authenticate.
-   [Triggering AI Employees](/builder/v2/api-reference/triggers) — use a JWT or API key to start runs from your own systems.
