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
| Scenario | Use API Keys | Use JWT (Login) |
|---|---|---|
| Backend service calling SALLY API | Yes | No |
| Cron job syncing fleet data | Yes | No |
| CI/CD pipeline running tests | Yes | No |
| User-facing web dashboard | No | Yes |
| Mobile driver app | No | Yes |
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
- Sign in to your SALLY dashboard at sally.appshore.in
- Navigate to Settings > API Keys
- Click Create API Key
- Enter a descriptive name (e.g., “TMS Integration - Production”, “Monitoring Service”)
- Select the desired permission scope
- Click Generate
- 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.productionUse 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 productionRotate Keys Regularly
Follow this rotation schedule:
| Environment | Rotation Frequency |
|---|---|
| Development | Every 90 days |
| Staging | Every 60 days |
| Production | Every 30 days |
| After compromise | Immediately |
Rotation procedure:
- Generate a new key
- Update all services to use the new key
- Verify the new key works in all environments
- 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
- Authentication — Learn about JWT authentication for user-facing apps
- Your First Route — Plan a route end-to-end
- Drivers API — Manage your driver fleet