Skip to content

Token Exchange

Token exchange allows you to convert API keys into short-lived JWT tokens for accessing data from services.

Base URL

All API endpoints use the following base URL:

BASE_URL = "https://api.fermi.dev/public/v1/identity"

Examples in this documentation use the full URL for clarity, but you should use the base URL in your code.

What is Token Exchange?

Token exchange is a process that:

  • Converts an API key into a short-lived JWT token
  • Provides tokens for service actor authentication
  • Tokens expire automatically after 15 minutes

How Token Exchange Works

1. You have an API key with provision:token:exchange scope
2. You have a service actor with specific capabilities
3. Call the token exchange endpoint with your API key and service actor ID
4. Receive a short-lived JWT token
5. Use the token for authenticated API requests
6. Token expires automatically after 15 minutes

Exchanging an API Key for a Token

Prerequisites

  • An API key with provision:token:exchange scope
  • A service actor ID

Exchange Request

Endpoint: POST /public/v1/identity/auth/token/exchange

curl -X POST https://api.fermi.dev/public/v1/identity/auth/token/exchange \
  -H "Authorization: Bearer fmk_live_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "serviceActorId": "507f1f77bcf86cd799439011"
  }'

Request Body:

Parameter Type Required Description
serviceActorId string Yes ID of the service actor to generate token for

Response:

{
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "expiresIn": 900,
  "expiresAt": 1710000900,
  "actorType": "service",
  "organisationId": "507f1f77bcf86cd799439011",
  "scopes": ["analytics:query", "analytics:write"]
}

Response Fields:

Field Type Description
token string JWT token to use for authenticated requests
expiresIn number Token expiration time in seconds (900 = 15 minutes)
expiresAt number Token expiration timestamp (Unix epoch)
actorType string Always "service" for service actor tokens
organisationId string Organization ID from the service actor
scopes string[] Capabilities granted to the service actor

Using the Token

Once you have the token, use it in the Authorization header:

curl -X POST https://api.fermi.dev/agents-service/api/v1/agentcore/chat \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
  -H "Content-Type: application/json" \
  -d '{
    "message": "What were last month sales?",
    "session_id": "my-chat-session-123"
  }'

Token Expiration

Tokens expire automatically after 15 minutes. When a token expires:

  1. Detect expiration: API returns 401 Unauthorized
  2. Exchange new token: Call the token exchange endpoint again
  3. Retry request: Use the new token

Example: Token Refresh Pattern

import requests

def get_service_actor_token(api_key, service_actor_id):
    """Exchange API key for service actor token"""
    response = requests.post(
        "https://api.fermi.dev/public/v1/identity/auth/token/exchange",
        headers={
            "Authorization": f"Bearer {api_key}",
            "Content-Type": "application/json"
        },
        json={"serviceActorId": service_actor_id}
    )
    response.raise_for_status()
    return response.json()

def make_authenticated_request(token, endpoint, data):
    """Make request with automatic token refresh"""
    headers = {
        "Authorization": f"Bearer {token}",
        "Content-Type": "application/json"
    }

    response = requests.post(endpoint, headers=headers, json=data)

    # If token expired, refresh and retry
    if response.status_code == 401:
        raise Exception("Token expired - exchange for a new token")

    return response

Error Handling

Missing Scope

{
  "status": 401,
  "message": "Unauthorized - Invalid API key, missing scope, or permission denied"
}

Solution: Ensure your API key has provision:token:exchange scope.

Service Actor Not Found

{
  "status": 404,
  "message": "Not Found - Service actor not found"
}

Solution: Verify the service actor ID is correct and belongs to your organization.

Organization Mismatch

{
  "status": 403,
  "message": "Service actor's parent organisation must match API key's organization"
}

Solution: Ensure the service actor belongs to the same organization as your API key.

Token Expired

{
  "status": 401,
  "message": "JWT verification failed - token is invalid or expired"
}

Solution: Exchange for a new token using the token exchange endpoint.

Complete Example

Python Example

import os
import requests

# Configuration
API_KEY = os.getenv("FERMI_API_KEY")
SERVICE_ACTOR_ID = os.getenv("FERMI_SERVICE_ACTOR_ID")
BASE_URL = "https://api.fermi.dev"

def exchange_token(api_key, service_actor_id):
    """Exchange API key for service actor token"""
    response = requests.post(
        f"{BASE_URL}/public/v1/identity/auth/token/exchange",
        headers={
            "Authorization": f"Bearer {api_key}",
            "Content-Type": "application/json"
        },
        json={"serviceActorId": service_actor_id}
    )
    response.raise_for_status()
    return response.json()

def send_chat(token, query):
    """Send an Ask Fermi message using a service actor token"""
    response = requests.post(
        f"{BASE_URL}/agents-service/api/v1/agentcore/chat",
        headers={
            "Authorization": f"Bearer {token}",
            "Content-Type": "application/json"
        },
        json={"message": query, "session_id": "my-chat-session-123"}
    )
    response.raise_for_status()
    return response.json()

# Main flow
try:
    # 1. Exchange API key for token
    token_response = exchange_token(API_KEY, SERVICE_ACTOR_ID)
    token = token_response["token"]
    expires_at = token_response["expiresAt"]

    print(f"Token expires at: {expires_at}")

    # 2. Use token for API calls
    result = send_chat(token, "What were last month sales?")
    print(f"Chat result: {result}")

except requests.exceptions.HTTPError as e:
    if e.response.status_code == 401:
        print("Token expired or invalid - exchange for a new token")
    else:
        print(f"API error: {e}")

JavaScript/TypeScript Example

const API_KEY = process.env.FERMI_API_KEY;
const SERVICE_ACTOR_ID = process.env.FERMI_SERVICE_ACTOR_ID;
const BASE_URL = 'https://api.fermi.dev';

async function exchangeToken(apiKey, serviceActorId) {
  const response = await fetch(`${BASE_URL}/public/v1/identity/auth/token/exchange`, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${apiKey}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ serviceActorId })
  });

  if (!response.ok) {
    throw new Error(`Token exchange failed: ${response.status}`);
  }

  return await response.json();
}

async function sendChat(token, query) {
  const response = await fetch(`${BASE_URL}/agents-service/api/v1/agentcore/chat`, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${token}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ message: query, session_id: 'my-chat-session-123' })
  });

  if (!response.ok) {
    throw new Error(`Chat request failed: ${response.status}`);
  }

  return await response.json();
}

// Main flow
(async () => {
  try {
    // 1. Exchange API key for token
    const tokenResponse = await exchangeToken(API_KEY, SERVICE_ACTOR_ID);
    const token = tokenResponse.token;
    const expiresAt = new Date(tokenResponse.expiresAt * 1000);

    console.log(`Token expires at: ${expiresAt.toISOString()}`);

    // 2. Use token for API calls
    const result = await sendChat(token, 'What were last month sales?');
    console.log('Chat result:', result);

  } catch (error) {
    console.error('Error:', error.message);
  }
})();

Next Steps