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:
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:exchangescope - 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:
- Detect expiration: API returns
401 Unauthorized - Exchange new token: Call the token exchange endpoint again
- 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¶
Solution: Ensure your API key has provision:token:exchange scope.
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¶
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¶
- Service Actors - Learn how to create service actors
- Scopes and Permissions - Understand available scopes
- API Reference - Explore API endpoints
- Code Examples - More complete examples