Authentication
SALLY supports three authentication methods depending on your use case:
| Method | Use Case | Header |
|---|---|---|
| Firebase + JWT | User-facing applications (dashboard, driver app) | Authorization: Bearer {accessToken} |
| API Keys | Server-to-server integrations | X-API-Key: sk_staging_YOUR_KEY |
| Mock Login | POC and local development | Authorization: Bearer {accessToken} |
Firebase + JWT (User-Facing Applications)
This is the primary authentication flow for end users (dispatchers, drivers, admins). The frontend authenticates with Firebase, then exchanges the Firebase token for a SALLY JWT.
Flow Overview
- User signs in with Firebase (email/password)
- Frontend sends the Firebase ID token to SALLY backend
- Backend verifies the Firebase token, looks up the user, and generates a SALLY JWT
- Backend returns an
accessTokenin the response body and sets arefreshTokenas an HTTP-only cookie - Subsequent API requests include the access token in the
Authorizationheader - When the access token expires, use the refresh endpoint (which reads the HTTP-only cookie) to get a new one
Step 1: Exchange Firebase Token
After the user authenticates with Firebase on the frontend, send the Firebase ID token to SALLY:
curl -X POST /api/v1/auth/firebase/exchange \
-H "Content-Type: application/json" \
-d '{
"firebaseToken": "eyJhbGciOiJSUzI1NiIs..."
}'Response:
{
"accessToken": "eyJhbGciOiJSUzI1NiIs...",
"user": {
"userId": "usr_8a2b3c4d",
"email": "dispatcher@acmefreight.com",
"role": "DISPATCHER",
"tenantId": "tnt_acme001"
}
}The refreshToken is set automatically as an HTTP-only cookie in the response. It is not included in the JSON body.
Step 2: Make Authenticated Requests
Include the access token in subsequent requests:
curl /api/v1/routes/plan \
-H "Authorization: Bearer eyJhbGciOiJSUzI1NiIs..." \
-H "Content-Type: application/json" \
-d '{ ... }'Step 3: Refresh the Access Token
When the access token expires, call the refresh endpoint. The refresh token is read from the HTTP-only cookie automatically:
curl -X POST /api/v1/auth/refresh \
-b cookies.txtResponse:
{
"accessToken": "eyJhbGciOiJSUzI1NiIs..."
}A new refreshToken cookie is also set in the response.
API Keys (Server-to-Server Integrations)
API keys are designed for backend services, scripts, and automated integrations that call the SALLY API without a user session.
Key Format
sk_staging_<32_random_characters>Example: sk_staging_abc123def456ghi789jkl012mno345
Production keys (coming soon) will use:
sk_live_<32_random_characters>Creating API Keys
Generate keys from the SALLY dashboard under Settings > API Keys, or create them via the API.
Making Authenticated Requests
Include the key in the X-API-Key header:
curl /api/v1/routes/plan \
-H "X-API-Key: sk_staging_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{ ... }'Alternatively, you can use the Authorization header with the Bearer scheme:
curl /api/v1/routes/plan \
-H "Authorization: Bearer sk_staging_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{ ... }'JavaScript Example
const response = await fetch('https://sally-api.apps.appshore.in/api/v1/routes/plan', {
method: 'POST',
headers: {
'X-API-Key': process.env.SALLY_API_KEY,
'Content-Type': 'application/json'
},
body: JSON.stringify({ /* your data */ })
});Python Example
import requests
response = requests.post(
'https://sally-api.apps.appshore.in/api/v1/routes/plan',
headers={'X-API-Key': 'sk_staging_YOUR_KEY'},
json={ ... }
)Mock Login (POC / Development)
For quick development and testing, SALLY provides a mock login endpoint that bypasses Firebase entirely. Pass a known user ID and receive a valid access token immediately.
curl -X POST /api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{
"user_id": "user_swift_disp_001"
}'Response:
{
"accessToken": "eyJhbGciOiJSUzI1NiIs...",
"user": {
"userId": "user_swift_disp_001",
"email": "dispatcher@swifthaul.com",
"role": "DISPATCHER",
"tenantId": "tnt_swift001"
}
}The refreshToken is set as an HTTP-only cookie (not included in the response body). Use the returned accessToken in subsequent requests just like the Firebase + JWT flow.
Development only. The mock login endpoint is intended for POC and local development. It will be disabled in production environments.
Error Responses
401 Unauthorized
Returned when:
- Token or API key is missing
- Token or API key is invalid or expired
- Token or API key has been revoked
{
"statusCode": 401,
"message": "Invalid or expired API key",
"error": "Unauthorized"
}429 Too Many Requests
Returned when you exceed the rate limit:
{
"statusCode": 429,
"message": "Rate limit exceeded. Try again in 3600 seconds.",
"error": "Too Many Requests"
}Response headers include:
X-RateLimit-Limit: Your rate limit (e.g., 1000)X-RateLimit-Remaining: Requests remaining in current windowX-RateLimit-Reset: Unix timestamp when the limit resets
Best Practices
Keep Credentials Secret
- Never commit API keys or tokens to version control
- Use environment variables for all credentials
- Rotate API keys periodically
// Good
const apiKey = process.env.SALLY_API_KEY;
// Bad
const apiKey = 'sk_staging_abc123...';Use the Right Auth Method
- Building a UI? Use Firebase + JWT
- Backend integration? Use API keys
- Quick testing? Use mock login
Handle Errors Gracefully
Always handle authentication errors in your client code:
const response = await fetch(url, { headers });
if (response.status === 401) {
// Token expired -- attempt refresh
const refreshRes = await fetch('/api/v1/auth/refresh', {
method: 'POST',
credentials: 'include'
});
if (refreshRes.ok) {
const { accessToken } = await refreshRes.json();
// Retry the original request with the new token
}
}
if (response.status === 429) {
// Rate limited -- implement exponential backoff
}Next Steps
- API Keys Setup - Generate and manage API keys
- Quickstart Guide - Make your first request
- Your First Route - End-to-end route planning tutorial