Customer Tokens

Customer tokens are short-lived bearer tokens that scope API access to a specific customer. They enable multi-tenant applications where customers make requests from frontends or mobile apps, with tier limits applied when the customer is subscribed.

When to Use Customer Tokens

Use customer tokens when:

  • Customers need to call the AI API from your frontend
  • Building mobile apps with AI features
  • You want usage tracked and limited per customer
  • You need per-customer tracking (tiered or tierless)

Use secret keys instead when:

  • Making server-side API calls from your backend
  • Running batch jobs or background processing
  • Requests aren’t attributable to a specific customer

Token Minting (Backend Required)

Customer tokens are minted by your backend using a secret key. This is the supported flow for client apps.

sequenceDiagram
    participant Client as Browser/Mobile
    participant Backend as Your Backend
    participant MR as ModelRelay

    Client->>Backend: 1. Request token
    Backend->>MR: 2. Mint token (secret key)
    MR-->>Backend: Customer token
    Backend-->>Client: 3. Return token
    Client->>MR: 4. API request (bearer token)
    MR-->>Client: Response

Backend Minting

Project scope is derived from the secret key. Do not include projectId/project_id in the request body—requests that include it are rejected.

Important: The customer must exist before minting a token. If you call customerToken() with a non-existent customerExternalId, you’ll get a 404 error. Use getOrCreateCustomerToken() to automatically create the customer if they don’t exist.

import { ModelRelay } from "@modelrelay/sdk";

const mr = ModelRelay.fromSecretKey(process.env.MODELRELAY_SECRET_KEY!);

// Mint a token for a customer (creates customer if needed)
const token = await mr.auth.getOrCreateCustomerToken({
  externalId: "user_42",       // Your user ID
  email: "user@example.com",   // Required for customer creation
});

// Return to frontend
res.json({
  token: token.token,
  expiresAt: token.expiresAt,
});
// Mint a token for a customer (creates customer if needed)
token, err := client.Auth.GetOrCreateCustomerToken(ctx, sdk.GetOrCreateCustomerTokenRequest{
    ExternalID: sdk.NewCustomerExternalID("user_42"),
    Email:      "user@example.com",
})
if err != nil {
    return err
}

// Return to frontend
json.NewEncoder(w).Encode(map[string]any{
    "token":      token.Token,
    "expires_at": token.ExpiresAt,
})
// Mint a token for a customer (creates customer if needed)
let token = client
    .auth()
    .get_or_create_customer_token(GetOrCreateCustomerTokenRequest {
        external_id: "user_42".into(),
        email: "user@example.com".into(),
        ..Default::default()
    })
    .await?;

// Return to frontend
let response = serde_json::json!({
    "token": token.token,
    "expires_at": token.expires_at,
});

Using customerToken() Directly

If you’ve already created the customer separately (e.g., during user registration), you can use customerToken() directly:

// Customer must already exist - returns 404 if not found
const token = await mr.auth.customerToken({
  customerExternalId: "user_42",
});
// Customer must already exist - returns 404 if not found
token, err := client.Auth.CustomerToken(ctx, sdk.CustomerTokenRequest{
    CustomerExternalID: sdk.NewCustomerExternalID("user_42"),
})
// Customer must already exist - returns 404 if not found
let token = client
    .auth()
    .customer_token(CustomerTokenRequest::for_external_id("user_42"))
    .await?;

Identifying Customers

Provide exactly one of:

  • customerId — ModelRelay’s internal UUID
  • customerExternalId — Your own user identifier

Using customerExternalId is recommended since it maps to your user system.

Token Lifetime

Setting Default Maximum
TTL 7 days 30 days

Override the default with ttlSeconds:

const token = await mr.auth.customerToken({
  customerExternalId: "user_42",
  ttlSeconds: 3600, // 1 hour
});

Shorter TTLs are more secure but require more frequent token refreshes.

Using Tokens

On the frontend, use the token as a bearer token:

Raw Fetch

const response = await fetch("https://api.modelrelay.ai/api/v1/responses", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${token}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    model: "claude-sonnet-4-5",
    input: [{ role: "user", content: "Hello!" }],
  }),
});

With the SDK

import { ModelRelay } from "@modelrelay/sdk";

// Initialize with the customer token
const mr = ModelRelay.fromToken(customerToken);

const answer = await mr.responses.text(
  "claude-sonnet-4-5",
  "You are a helpful assistant.",
  "Hello!"
);

Token Providers

For automatic token refresh when using the backend minting approach:

import { ModelRelay } from "@modelrelay/sdk";

// In your backend: create a /api/token endpoint
app.get("/api/token", async (req, res) => {
  const mr = ModelRelay.fromSecretKey(process.env.MODELRELAY_SECRET_KEY!);
  const token = await mr.auth.customerToken({
    customerExternalId: req.user.id,
  });
  res.json({ token: token.token, expiresAt: token.expiresAt });
});

// In your frontend: use a token provider
const mr = new ModelRelay({
  tokenProvider: {
    getToken: async () => {
      const res = await fetch("/api/token");
      const { token } = await res.json();
      return token;
    },
  },
});

Token Response Fields

When you obtain a token, the response includes:

Field Type Description
token string The JWT bearer token
expiresAt Date When the token expires
expiresIn number Seconds until expiration
projectId string Project the token is scoped to
customerId string Internal customer UUID
customerExternalId string Your customer identifier
tierCode string Customer’s tier (e.g., “free”, “pro”)

Security Considerations

Token Storage

  • Store tokens in memory or short-lived browser storage (sessionStorage)
  • Avoid localStorage for sensitive tokens
  • Never expose secret keys to the frontend

Token Validation

Tokens are validated on every request:

  • Signature verification (tamper-proof)
  • Expiration check
  • Customer and project scope enforcement
  • Tier limits applied automatically

Best Practices

  1. Keep secret keys server-side — Use backend minting, never expose secret keys
  2. Use short TTLs for sensitive apps — 1 hour is reasonable for high-security apps
  3. Handle expiration gracefully — Implement token refresh before expiry
  4. Log token issuance — Track which users get tokens for auditing

Next Steps