Skip to content

Authentication API

The Authentication API provides endpoints for authentication and identity management.

Base URL

https://api.fermi.dev/public/v1/identity

Authentication

Most authentication endpoints require an API key. Some endpoints may require user JWT tokens.

Authorization: Bearer <api-key-or-token>

Endpoints

Exchange API Key for Service Actor Token

Exchange a long-lived API key for a short-lived JWT token for a service actor.

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

Authentication: API key with provision:token:exchange scope

Request:

{
  "serviceActorId": "507f1f77bcf86cd799439011"
}

Request Parameters:

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 for authenticated requests
expiresIn number Token expiration time in seconds (default: 900)
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

Example:

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

Error Responses

Missing Scope

{
  "status": 401,
  "error": "Unauthorized",
  "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,
  "error": "Not Found",
  "message": "Not Found - Service actor not found"
}

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

Organization Mismatch

{
  "status": 403,
  "error": "Forbidden",
  "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.

Rate Limits

  • Token Exchange: 100 requests per minute per API key

Best Practices

  1. Cache tokens until near expiration (tokens last 15 minutes)
  2. Handle expiration gracefully with automatic refresh
  3. Use service actors for automated systems
  4. Monitor token usage and exchange frequency

Examples

Python Example

import requests
import os
import time

api_key = os.getenv("FERMI_API_KEY")
service_actor_id = os.getenv("FERMI_SERVICE_ACTOR_ID")

def exchange_token(api_key, service_actor_id):
    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()

# Exchange for token
token_data = exchange_token(api_key, service_actor_id)
token = token_data["token"]
expires_at = token_data["expiresAt"]

print(f"Token expires at: {time.ctime(expires_at)}")
print(f"Scopes: {token_data['scopes']}")

JavaScript Example

const apiKey = process.env.FERMI_API_KEY;
const serviceActorId = process.env.FERMI_SERVICE_ACTOR_ID;

async function exchangeToken(apiKey, serviceActorId) {
  const response = await fetch(
    'https://api.fermi.dev/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();
}

// Usage
exchangeToken(apiKey, serviceActorId)
  .then(data => {
    console.log(`Token expires at: ${new Date(data.expiresAt * 1000).toISOString()}`);
    console.log(`Scopes: ${data.scopes.join(', ')}`);
  })
  .catch(error => console.error('Error:', error));

Next Steps