Authentication

Simbee uses JWT bearer tokens for API authentication. This guide walks through the complete flow: signing up, creating API keys, exchanging credentials for tokens, and making authenticated requests.

Overview

The authentication flow has three steps:

  1. Sign up — Create a tenant via POST /auth/signup. You receive a client, owner user, and an initial JWT.
  2. Create an API key — Use the initial token to create a named API key via POST /api/v1/clients/:client_id/api_keys. The raw key is shown only once.
  3. Authenticate — Exchange credentials for a JWT via POST /auth/token. Include the token as Authorization: Bearer <token> on all subsequent requests.

Tokens are short-lived (15 minutes by default). When a token expires, re-authenticate with POST /auth/token.

1. Sign up

Create a new tenant by providing an email, password, and company name. The response includes your client, an owner user, and a JWT you can use immediately.

curl -X POST https://api.simbee.io/auth/signup \
  -H "Content-Type: application/json" \
  -d '{
    "email": "alice@example.com",
    "password": "a-strong-password",
    "company_name": "Acme Corp"
  }'

Response

// 201 Created
{
  "client": {
    "id": "cl_abc123",
    "slug": "acme-corp",
    "name": "Acme Corp",
    "status": "active",
    "tier": "graph"
  },
  "user": {
    "id": "cu_xyz789",
    "email": "alice@example.com",
    "role": "owner"
  },
  "token": "eyJhbGciOiJFZERTQSIs...",
  "scopes": ["admin", "read", "write"],
  "expires_in": 900
}

Save the client.id — you need it for all subsequent API calls. The initial token is valid for 15 minutes.

2. Create an API key

API keys are used for programmatic access. Create one using the initial token from signup. The raw key is returned only once — store it securely.

curl -X POST https://api.simbee.io/api/v1/clients/cl_abc123/api_keys \
  -H "Authorization: Bearer eyJhbGciOiJFZERTQSIs..." \
  -H "Content-Type: application/json" \
  -d '{ "name": "production" }'

Response

// 201 Created
{
  "raw_key": "simbee_aBcDeFgHiJkLmNoPqRsTuVwXyZ",
  "record": {
    "id": "ak_def456",
    "client_id": "cl_abc123",
    "name": "production",
    "fingerprint": "fp_a1b2c3",
    "revoked": false,
    "created_at": "2026-04-11T12:00:00Z"
  }
}

3. Exchange credentials for a JWT

Use /auth/token to exchange credentials for a short-lived JWT. Tokens are valid for 15 minutes (900 seconds) by default.

Email login

The simplest way to authenticate. Provide the email and password used during signup.

curl -X POST https://api.simbee.io/auth/token \
  -H "Content-Type: application/json" \
  -d '{
    "email": "alice@example.com",
    "password": "a-strong-password"
  }'

Programmatic login

For service-to-service or automated flows, authenticate with client_id and user_id instead of email.

curl -X POST https://api.simbee.io/auth/token \
  -H "Content-Type: application/json" \
  -d '{
    "client_id": "cl_abc123",
    "user_id": "owner",
    "password": "a-strong-password"
  }'

Response

// 200 OK
{
  "token": "eyJhbGciOiJFZERTQSIs...",
  "scopes": ["admin", "read", "write"],
  "expires_in": 900,
  "user": {
    "id": "cu_xyz789",
    "email": "alice@example.com",
    "role": "owner"
  },
  "client": {
    "id": "cl_abc123",
    "slug": "acme-corp",
    "name": "Acme Corp"
  }
}

4. Make authenticated requests

Include the JWT in the Authorization header as a Bearer token on every API request.

# List users
curl https://api.simbee.io/api/v1/users \
  -H "Authorization: Bearer eyJhbGciOiJFZERTQSIs..."

# Create a user
curl -X POST https://api.simbee.io/api/v1/users \
  -H "Authorization: Bearer eyJhbGciOiJFZERTQSIs..." \
  -H "Content-Type: application/json" \
  -d '{ "external_id": "user_42", "traits": { "name": "Bob" } }'

5. Token refresh

Tokens expire after expires_in seconds (default: 900s). When expired, the API returns 401 Unauthorized. Call POST /auth/token again with the same credentials to get a new token.

Recommended pattern:

  1. Store the token and its expiry time (now + expires_in).
  2. Before each request, check if the token is within 60s of expiry.
  3. If near expiry, call /auth/token to refresh.
  4. On 401 responses, re-authenticate and retry the request once.
class SimbeeAuth {
  private token: string | null = null;
  private expiresAt = 0;

  constructor(
    private email: string,
    private password: string,
  ) {}

  async getToken(): Promise<string> {
    if (this.token && Date.now() < this.expiresAt - 60_000) {
      return this.token;
    }
    const auth = new AuthenticationApi(
      new Configuration({ basePath: "https://api.simbee.io" })
    );
    const { data } = await auth.createAuthToken({
      email: this.email,
      password: this.password,
    });
    this.token = data.token;
    this.expiresAt = Date.now() + data.expires_in * 1000;
    return this.token;
  }
}

6. JWKS verification (backend-to-backend)

To verify Simbee JWTs independently without calling the API, fetch public keys from the JWKS endpoint:

GET https://api.simbee.io/.well-known/jwks.json

The response contains the public keys used to sign session tokens. Use any standard JWT library to verify tokens against these keys.

import jwt from "jsonwebtoken";
import jwksClient from "jwks-rsa";

const client = jwksClient({
  jwksUri: "https://api.simbee.io/.well-known/jwks.json",
  cache: true,
  rateLimit: true,
});

function getKey(header, callback) {
  client.getSigningKey(header.kid, (err, key) => {
    callback(err, key?.getPublicKey());
  });
}

jwt.verify(token, getKey, { algorithms: ["EdDSA"] }, (err, decoded) => {
  if (err) throw err;
  console.log(decoded.sub); // User ID
  console.log(decoded.aud); // Client ID
});
Cache the JWKS response. Public keys rotate infrequently. Cache for at least 5 minutes. If verification fails with an unknown kid, refresh the cache and retry once before rejecting.