---
title: Authentication
description: API keys, user tokens, and how to keep them safe on the Seamless OS API.
---

The Seamless OS API authenticates a caller in two layers. An API key carries the trust
between your service and ours. A user token narrows one request to the permissions of one
user.

## Quick start

Every request needs an API key in the `X-API-Key` header. For an operation on behalf of one
user, add a JWT token in the `Authorization` header.

```bash
# API key only (full permissions)
curl "{BASE_URL}/customers" \
  -H "X-API-Key: $API_KEY"

# API key + user token (user's permissions only)
curl "{BASE_URL}/customers" \
  -H "X-API-Key: $API_KEY" \
  -H "Authorization: Bearer $USER_TOKEN"
```

## API keys

An API key carries the trust between your application and the Seamless OS API. It grants full
access to every resource in the scope of your organization.

### Security model

**An API key gives complete access to the system.** Treat it like a root password:

- **Keep it out of frontend code.** An API key belongs on your own backend servers.
- **Rotate it.** Generate a new key every month, and again after a security incident.
- **Separate your environments.** Use a different key for development, for staging, and for
  production.
- **Store it safely.** Put the key in an environment variable or in a credential manager.

### Getting an API key

Create and manage your API keys in the Seamless OS portal. Each key belongs to your
organization and reaches every resource that you have permission to manage.

### Usage

Put your API key in the `X-API-Key` header of every request:

```bash
curl "{BASE_URL}/subscriptions" \
  -H "X-API-Key: $API_KEY" \
  -H "Content-Type: application/json"
```

## User authentication

With a user token you can act on behalf of one user. The token limits the request to what that
user has permission to reach.

### JWT bearer tokens

A user token is a JWT. Put it in the `Authorization` header with the `Bearer` scheme:

```bash
# Include user token in Authorization header
curl "{BASE_URL}/subscriptions" \
  -H "X-API-Key: $API_KEY" \
  -H "Authorization: Bearer $USER_TOKEN"
```

### Permission scoping

When a request carries both an API key and a user token, three rules apply:

1. The **API key** authenticates the right of your application to use the API.
2. The **user token** identifies the one user and their permissions.
3. The **effective permissions** are the intersection of the two.

Your API key has full access, but the request reaches only what the authenticated user can
reach.

**Example scenarios:**

- An admin user token reaches every customer and every subscription.
- A limited user token reaches only the customer accounts assigned to that user.
- A support user token reads a subscription, but it cannot change one.

### Integration patterns

**Backend integration.** Send the API key alone for a system-level operation: bulk
processing, reporting, or an administrative task.

**User-facing operations.** Add a user token to every action that one user starts in your
application.

```bash
# System operation - API key only
curl "{BASE_URL}/subscriptions" \
  -H "X-API-Key: $API_KEY"

# User operation - API key + user token
curl "{BASE_URL}/subscriptions" \
  -H "X-API-Key: $API_KEY" \
  -H "Authorization: Bearer $USER_TOKEN"
```

## Security best practices

### API key management

- **Server side only.** Never put an API key in client-side JavaScript or in a mobile app.
- **Environment variables.** Store the key in the `API_KEY` environment variable.
- **Key rotation.** Replace the key on a schedule, and at once after a suspected compromise.
- **Monitoring.** Watch the traffic of each API key, so an unusual pattern reaches you.

### Token handling

- **Secure transmission.** Send every request over HTTPS.
- **Token expiration.** Refresh an access token before it expires.
- **Minimal scope.** Request only the permissions that your application needs.

### Request security
```javascript
// ✅ Good - Secure backend request
const response = await fetch('/api/v2/orders', {
  method: 'POST',
  headers: {
    'X-API-Key': process.env.API_KEY, // From secure environment
    Authorization: `Bearer ${validUserToken}`, // From authenticated session
    'Content-Type': 'application/json',
  },
  body: JSON.stringify(orderData),
});

// ❌ Bad - Never expose API keys client-side
const response = await fetch('/api/v2/orders', {
  headers: {
    'X-API-Key': 'api_123abc456def', // Exposed in browser!
  },
});
```

## Email authentication flow

The Seamless OS API has a passwordless email flow that gives your application a JWT token.
The flow has two steps, and it sends a code to the email address of the user.

### Step 1: Start email login

Ask the API to send a login code to the email address of the user:

```bash
curl -X POST "{BASE_URL}/auth/email/start" \
  -H "X-API-Key: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "user@example.com"
  }'
```

The user gets an email with a verification code.

### Step 2: Verify the code

After the user enters the code, send it to the API. The response carries a JWT token:

```bash
curl -X POST "{BASE_URL}/auth/email/verify" \
  -H "X-API-Key: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "user@example.com",
    "code": "123456"
  }'
```

Send the `accessToken` as a Bearer token on every later request. Store the `refreshToken`
safely. With it you can get a new access token when the current one expires.

## Troubleshooting

**401 Unauthorized.** The credentials are absent or invalid. Make sure that your API key is
valid, that the `X-API-Key` header carries it in the correct format, and that the user token
is not expired.

**403 Forbidden.** The credentials are valid, but they do not carry the permissions of the
operation. With a user token, make sure that the user has those permissions. The API applies
the most restrictive permissions of the two: what your API key can do, and what the user is
assigned.
