Getting StartedAuthentication

Authentication

SALLY supports three authentication methods depending on your use case:

MethodUse CaseHeader
Firebase + JWTUser-facing applications (dashboard, driver app)Authorization: Bearer {accessToken}
API KeysServer-to-server integrationsX-API-Key: sk_staging_YOUR_KEY
Mock LoginPOC and local developmentAuthorization: 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

  1. User signs in with Firebase (email/password)
  2. Frontend sends the Firebase ID token to SALLY backend
  3. Backend verifies the Firebase token, looks up the user, and generates a SALLY JWT
  4. Backend returns an accessToken in the response body and sets a refreshToken as an HTTP-only cookie
  5. Subsequent API requests include the access token in the Authorization header
  6. 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.txt

Response:

{
  "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.

Warning

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 window
  • X-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