Syft Space uses bearer token authentication for API requests.
Authentication methods
There are two authentication methods depending on your use case:
Local authentication
SyftHub authentication
Used when accessing your own Syft Space instance directly.How it works:
- Login with email and password
- Receive JWT bearer token
- Include token in subsequent requests
Best for:
- Managing your own Space
- Development and testing
- Direct API access
Used when querying published endpoints through the SyftHub marketplace.How it works:
- Authenticate with SyftHub
- Receive satellite token
- Use token to query any published endpoint
Best for:
- Querying endpoints on other Spaces
- Marketplace integration
- Multi-Space queries
Local authentication
Register account
Create a new account:
curl -X POST http://localhost:8080/api/v1/auth/register \
-H "Content-Type: application/json" \
-d '{
"email": "[email protected]",
"password": "secure-password",
"tenant_name": "my-space"
}'
Response:
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"email": "[email protected]",
"tenant_name": "my-space",
"created_at": "2024-01-15T10:30:00Z"
}
Login
Get an access token:
curl -X POST http://localhost:8080/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{
"email": "[email protected]",
"password": "secure-password"
}'
Response:
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "bearer",
"expires_in": 3600
}
Using the token
Include the token in the Authorization header:
curl -X GET http://localhost:8080/api/v1/datasets/ \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
Headers:
Authorization: Bearer <access_token>
Tokens expire after 1 hour (3600 seconds). Login again to get a new token.
SyftHub authentication
Get satellite token
Authenticate with SyftHub to get a satellite token:
Login or register
Create an account or login with existing credentials
Generate token
Navigate to Settings → API Tokens and create a new token
Copy token
Copy the satellite token to use in your requests
Query with satellite token
Use the satellite token to query any published endpoint:
curl -X POST https://space-url.com/api/v1/endpoints/my-docs/query \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <satellite-token>" \
-d '{
"messages": [
{"role": "user", "content": "What are the main topics?"}
]
}'
How it works:
- You send query with satellite token
- Space validates token with SyftHub
- SyftHub confirms identity and permissions
- Space processes query if authorized
- Usage is tracked for billing/analytics
Local JWT token
Local tokens are JSON Web Tokens (JWT) with this structure:
{
"header": {
"alg": "HS256",
"typ": "JWT"
},
"payload": {
"email": "[email protected]",
"tenant_name": "my-space",
"exp": 1705318200,
"iat": 1705314600
},
"signature": "..."
}
Claims:
email - User’s email address
tenant_name - Tenant context
exp - Expiration timestamp (Unix)
iat - Issued at timestamp (Unix)
Satellite token
Satellite tokens are opaque tokens issued by SyftHub:
Prefix indicates environment:
sat_live_ - Production
sat_test_ - Testing/sandbox
Security best practices
- Never commit tokens to version control
- Store in environment variables or secret manager
- Use different tokens for different environments
- Rotate tokens regularly
Protect tokens in transit
- Always use HTTPS in production
- Never send tokens in URLs or query parameters
- Use Authorization header, not cookies
- Validate SSL certificates
def make_request(url, token):
response = requests.get(
url,
headers={"Authorization": f"Bearer {token}"}
)
if response.status_code == 401:
# Token expired, re-authenticate
token = login()
response = requests.get(
url,
headers={"Authorization": f"Bearer {token}"}
)
return response
Revoke compromised tokens
If a token is compromised:
- Logout from the application
- Change your password
- Generate new tokens
- Update applications using the old token
Multi-tenancy
For programmatic access to multiple tenants:
Authorization: Bearer <token>
X-Tenant-Name: my-other-space
The X-Tenant-Name header overrides the tenant in the JWT token. Only works if:
- Token belongs to a user with access to the specified tenant
- User has appropriate permissions in that tenant
Example:
curl -X GET http://localhost:8080/api/v1/datasets/ \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \
-H "X-Tenant-Name: another-space"
Testing authentication
Verify your token is working:
# Get current user info
curl -X GET http://localhost:8080/api/v1/auth/me \
-H "Authorization: Bearer <token>"
Response:
{
"email": "[email protected]",
"tenant_name": "my-space",
"created_at": "2024-01-15T10:30:00Z"
}
If authentication fails:
{
"detail": "Could not validate credentials"
}
Error handling
401 Unauthorized
Token is missing, invalid, or expired:
{
"detail": "Not authenticated"
}
Solutions:
- Include Authorization header
- Login to get new token
- Check token format (“Bearer YOUR_TOKEN”)
403 Forbidden
Token is valid but lacks permissions:
{
"detail": "Insufficient permissions"
}
Solutions:
- Check tenant access
- Verify endpoint policies
- Contact Space owner for access
Code examples
Python with token refresh
import requests
from datetime import datetime, timedelta
class AuthenticatedClient:
def __init__(self, base_url: str, email: str, password: str):
self.base_url = base_url
self.email = email
self.password = password
self.token = None
self.token_expiry = None
def login(self):
response = requests.post(
f"{self.base_url}/api/v1/auth/login",
json={"email": self.email, "password": self.password}
)
response.raise_for_status()
data = response.json()
self.token = data["access_token"]
self.token_expiry = datetime.now() + timedelta(
seconds=data["expires_in"]
)
def get_headers(self):
# Refresh token if expired or expiring soon
if not self.token or datetime.now() >= self.token_expiry - timedelta(minutes=5):
self.login()
return {
"Authorization": f"Bearer {self.token}",
"Content-Type": "application/json"
}
def get(self, path: str):
response = requests.get(
f"{self.base_url}{path}",
headers=self.get_headers()
)
response.raise_for_status()
return response.json()
# Usage
client = AuthenticatedClient(
base_url="http://localhost:8080",
email="[email protected]",
password="secure-password"
)
datasets = client.get("/api/v1/datasets/")
JavaScript with automatic retry
class AuthenticatedClient {
constructor(baseUrl, email, password) {
this.baseUrl = baseUrl
this.email = email
this.password = password
this.token = null
}
async login() {
const response = await fetch(`${this.baseUrl}/api/v1/auth/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email: this.email,
password: this.password
})
})
if (!response.ok) throw new Error('Login failed')
const data = await response.json()
this.token = data.access_token
}
async request(path, options = {}) {
if (!this.token) await this.login()
const response = await fetch(`${this.baseUrl}${path}`, {
...options,
headers: {
...options.headers,
'Authorization': `Bearer ${this.token}`,
'Content-Type': 'application/json'
}
})
// Retry once on 401
if (response.status === 401) {
await this.login()
return fetch(`${this.baseUrl}${path}`, {
...options,
headers: {
...options.headers,
'Authorization': `Bearer ${this.token}`,
'Content-Type': 'application/json'
}
})
}
return response
}
}
// Usage
const client = new AuthenticatedClient(
'http://localhost:8080',
'[email protected]',
'secure-password'
)
const response = await client.request('/api/v1/datasets/')
const datasets = await response.json()