telnesstech

Authentication

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.

# 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 key only (full permissions)
const response = await fetch('{BASE_URL}/customers', {
  headers: {
    'X-API-Key': process.env.API_KEY,
  },
});

// API key + user token (user's permissions only)
const userResponse = await fetch('{BASE_URL}/customers', {
  headers: {
    'X-API-Key': process.env.API_KEY,
    Authorization: `Bearer ${userToken}`,
  },
});

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:

curl "{BASE_URL}/subscriptions" \
  -H "X-API-Key: $API_KEY" \
  -H "Content-Type: application/json"
const response = await fetch('{BASE_URL}/subscriptions', {
  headers: {
    'X-API-Key': process.env.API_KEY,
    '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:

# Include user token in Authorization header
curl "{BASE_URL}/subscriptions" \
  -H "X-API-Key: $API_KEY" \
  -H "Authorization: Bearer $USER_TOKEN"
// Include user token in Authorization header
const response = await fetch('{BASE_URL}/subscriptions', {
  headers: {
    'X-API-Key': process.env.API_KEY,
    Authorization: `Bearer ${userToken}`,
  },
});

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.

# 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"
// System operation - API key only
const allSubscriptions = await fetch('{BASE_URL}/subscriptions', {
  headers: { 'X-API-Key': apiKey },
});

// User operation - API key + user token
const userSubscriptions = await fetch('{BASE_URL}/subscriptions', {
  headers: {
    'X-API-Key': apiKey,
    Authorization: `Bearer ${userToken}`,
  },
});

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

// ✅ 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:

curl -X POST "{BASE_URL}/auth/email/start" \
  -H "X-API-Key: $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "user@example.com"
  }'
const response = await fetch('{BASE_URL}/auth/email/start', {
  method: 'POST',
  headers: {
    'X-API-Key': process.env.API_KEY,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    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:

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"
  }'
const response = await fetch('{BASE_URL}/auth/email/verify', {
  method: 'POST',
  headers: {
    'X-API-Key': process.env.API_KEY,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    email: 'user@example.com',
    code: '123456',
  }),
});

const { accessToken, refreshToken } = await response.json();

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.