API Keys

API keys provide a simple, long-lived authentication method for server-to-server integrations. Use them when your backend services need to communicate with the SALLY API without user-interactive login flows.

When to Use API Keys

ScenarioUse API KeysUse JWT (Login)
Backend service calling SALLY APIYesNo
Cron job syncing fleet dataYesNo
CI/CD pipeline running testsYesNo
User-facing web dashboardNoYes
Mobile driver appNoYes

API keys are scoped to your tenant and carry the permissions of the user who created them. They do not expire automatically but can be revoked at any time.

Creating an API Key

Via the Dashboard

  1. Sign in to your SALLY dashboard at sally.appshore.in
  2. Navigate to Settings > API Keys
  3. Click Create API Key
  4. Enter a descriptive name (e.g., “TMS Integration - Production”, “Monitoring Service”)
  5. Select the desired permission scope
  6. Click Generate
  7. Copy the key immediately — it is only displayed once

Via the API

You can also create API keys programmatically. Authenticate with a JWT token first, then call the API keys endpoint:

curl -X POST https://sally-api.apps.appshore.in/api/v1/api-keys \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Fleet Sync Service",
    "description": "Used by the nightly fleet data synchronization job"
  }'

Response:

{
  "id": "ak_7f3a9b2c-1d4e-4f5a-8b6c-9d0e1f2a3b4c",
  "name": "Fleet Sync Service",
  "description": "Used by the nightly fleet data synchronization job",
  "key": "sk_staging_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6",
  "prefix": "sk_staging_a1b2...o5p6",
  "createdAt": "2026-02-10T14:30:00Z",
  "lastUsedAt": null,
  "isActive": true
}

Save the key value securely. You will not be able to retrieve it again.

Using API Keys

Pass your API key in the X-API-Key header on every request:

curl

curl https://sally-api.apps.appshore.in/api/v1/drivers \
  -H "X-API-Key: sk_staging_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6"

JavaScript (fetch)

const SALLY_API_KEY = process.env.SALLY_API_KEY;
const BASE_URL = "https://sally-api.apps.appshore.in/api/v1";
 
const response = await fetch(`${BASE_URL}/drivers`, {
  headers: {
    "X-API-Key": SALLY_API_KEY,
  },
});
 
const drivers = await response.json();

Python (requests)

import os
import requests
 
API_KEY = os.environ["SALLY_API_KEY"]
BASE_URL = "https://sally-api.apps.appshore.in/api/v1"
 
response = requests.get(
    f"{BASE_URL}/drivers",
    headers={"X-API-Key": API_KEY},
)
 
drivers = response.json()

You can also use the Authorization: Bearer header with an API key as an alternative:

curl https://sally-api.apps.appshore.in/api/v1/drivers \
  -H "Authorization: Bearer sk_staging_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6"

Both methods are equivalent. The X-API-Key header is recommended for server-to-server calls to clearly distinguish API key auth from JWT auth.

Managing API Keys

Listing Keys

Retrieve all API keys for your account:

curl https://sally-api.apps.appshore.in/api/v1/api-keys \
  -H "Authorization: Bearer YOUR_JWT_TOKEN"

Response:

{
  "data": [
    {
      "id": "ak_7f3a9b2c-1d4e-4f5a-8b6c-9d0e1f2a3b4c",
      "name": "Fleet Sync Service",
      "prefix": "sk_staging_a1b2...o5p6",
      "createdAt": "2026-02-10T14:30:00Z",
      "lastUsedAt": "2026-02-10T18:45:00Z",
      "requestCount": 1247,
      "isActive": true
    },
    {
      "id": "ak_2e4f6a8b-0c1d-2e3f-4a5b-6c7d8e9f0a1b",
      "name": "CI/CD Pipeline",
      "prefix": "sk_staging_x9y8...c3d4",
      "createdAt": "2026-01-15T10:00:00Z",
      "lastUsedAt": "2026-02-09T22:30:00Z",
      "requestCount": 8934,
      "isActive": true
    }
  ]
}

The full key value is never returned after creation. Only the prefix (first and last 4 characters) is shown.

Revoking a Key

Revoke a key immediately when it is compromised or no longer needed:

curl -X DELETE https://sally-api.apps.appshore.in/api/v1/api-keys/ak_7f3a9b2c-1d4e-4f5a-8b6c-9d0e1f2a3b4c \
  -H "Authorization: Bearer YOUR_JWT_TOKEN"

Response:

{
  "id": "ak_7f3a9b2c-1d4e-4f5a-8b6c-9d0e1f2a3b4c",
  "name": "Fleet Sync Service",
  "isActive": false,
  "revokedAt": "2026-02-10T19:00:00Z"
}

Revoked keys stop working immediately. Any in-flight requests using a revoked key will receive a 401 Unauthorized response. Revocation cannot be undone — generate a new key instead.

Key Types

Staging Keys

  • Prefix: sk_staging_
  • Environment: Staging API (sally-api.apps.appshore.in)
  • Rate limit: 1,000 requests/hour
  • Use for: Development, testing, CI/CD pipelines

Production Keys

  • Prefix: sk_live_
  • Environment: Production API
  • Rate limit: 10,000 requests/hour
  • Use for: Live applications and integrations

Security Best Practices

Store Keys in Environment Variables

Never hardcode API keys in your source code:

# .env (add to .gitignore)
SALLY_API_KEY=sk_staging_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6
// Good -- read from environment
const apiKey = process.env.SALLY_API_KEY;
 
// Bad -- hardcoded in source
const apiKey = "sk_staging_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6";

Add .env to .gitignore

Ensure environment files are never committed:

# .gitignore
.env
.env.local
.env.production

Use Secret Managers in Production

For production deployments, use your platform’s secret management:

AWS Secrets Manager:

aws secretsmanager create-secret \
  --name sally/api-key \
  --secret-string "sk_live_your_production_key"

GitHub Actions:

env:
  SALLY_API_KEY: ${{ secrets.SALLY_API_KEY }}

Vercel:

vercel env add SALLY_API_KEY production

Rotate Keys Regularly

Follow this rotation schedule:

EnvironmentRotation Frequency
DevelopmentEvery 90 days
StagingEvery 60 days
ProductionEvery 30 days
After compromiseImmediately

Rotation procedure:

  1. Generate a new key
  2. Update all services to use the new key
  3. Verify the new key works in all environments
  4. Revoke the old key

Principle of Least Privilege

Create separate keys for each service or integration. If one key is compromised, you only need to rotate that specific key, not all of them.

Monitor Key Usage

Review the API Keys dashboard regularly for:

  • Unexpected spikes in request count
  • Keys used from unfamiliar IP addresses
  • Keys that have not been used recently (candidates for revocation)
  • Requests near the rate limit threshold

Troubleshooting

”Invalid or expired API key” (401)

Possible causes:

  • Key was revoked
  • Key has a typo (check for extra whitespace)
  • Using a staging key against a production endpoint (or vice versa)
  • Key was never activated

Solution: Verify the key prefix matches your environment, or generate a new key.

”Rate limit exceeded” (429)

Possible causes:

  • Exceeding the requests-per-hour limit for your key type
  • Missing retry logic with backoff

Solution: Implement exponential backoff and check the X-RateLimit-Reset header:

async function callWithRetry(url, options, maxRetries = 3) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    const response = await fetch(url, options);
 
    if (response.status === 429) {
      const resetTime = response.headers.get("X-RateLimit-Reset");
      const waitMs = (parseInt(resetTime) * 1000) - Date.now();
      await new Promise((resolve) => setTimeout(resolve, Math.max(waitMs, 1000)));
      continue;
    }
 
    return response;
  }
 
  throw new Error("Max retries exceeded");
}

“Forbidden” (403)

Possible causes:

  • Key does not have sufficient permissions for the requested resource
  • Tenant is suspended or inactive

Solution: Check the key’s permission scope in the dashboard, or contact your account administrator.

Next Steps