API Key Management
API clients provide secure authentication for accessing the Nafsi AI platform via REST APIs and SDKs.
Overview
API clients consist of:
- Client ID: Public identifier (safe to expose in frontend)
- Client Secret: Private key (must be kept secure)
- Environment: Production or Sandbox
- Rate Limit: Requests per minute
- Status: Active, Inactive, or Revoked
Creating an API Client
- Navigate to API Keys in the sidebar
- Click Create New Client
- Fill in the details:
| Field | Description | Required |
|---|---|---|
| Client Name | Descriptive name (e.g., "Production iOS App") | Yes |
| Environment | Production or Sandbox | Yes |
| Rate Limit | Requests per minute (default: 100) | Yes |
- Click Create Client
Client Credentials
After creation, you'll see:
✅ API Client Created Successfully!
Client ID: 86f59fee-df8a-11f0-8e0f-9ef6b0f7fa9e
Client Secret: sk_live_abc123xyz789...
⚠️ IMPORTANT: Save these credentials now!
The client secret will not be shown again.
What to do with credentials:
- Copy Immediately: Click copy buttons
- Store Securely:
- Environment variables
- Secrets manager (AWS Secrets Manager, HashiCorp Vault)
- Password manager
- Never Commit to Git: Add to
.gitignore - Never Expose in Frontend: Only use in backend code
Why the secret matters:
- Required for creating workflows (generates refresh token)
- Required for direct API authentication
- Cannot be retrieved later (only regenerated)
Client Secret and Workflow Tokens
The Authentication Flow
graph LR
A[Create API Client] --> B[Get client_id + secret]
B --> C[Create Workflow]
C --> D[Enter client_secret]
D --> E{Validate}
E -->|Valid| F[Generate refresh_token]
E -->|Invalid| G[Show error]
F --> H[Attach token to workflow]
H --> I[SDK/API can now authenticate]
G --> D
Why Refresh Tokens?
Problem: Exposing client secret in frontend code is insecure
Solution: Workflows have refresh tokens
How it works:
- During workflow creation, you enter client_secret
- System validates and generates a refresh_token (JWT)
- Token is attached to the workflow
- SDK/API uses workflow_id to authenticate
- Backend looks up workflow's refresh_token
- Authentication succeeds without exposing secret
Benefits: ✅ Client secret never exposed in frontend ✅ SDK integration is secure ✅ Tokens scoped to specific workflow ✅ Tokens can be invalidated independently
Learn more about authentication →
Managing API Clients
Viewing Clients
The API clients list shows:
| Column | Description |
|---|---|
| Name | Client name |
| Client ID | Public identifier (truncated) |
| Environment | Production or Sandbox |
| Created | Creation date |
| Last Used | Last API call timestamp |
| Rate Limit | Requests per minute |
| Status | Active / Inactive / Revoked |
| Actions | Edit / Regenerate Secret / Revoke / View Usage |
Regenerating Client Secret
When to regenerate:
- Secret compromised or exposed
- Periodic security rotation (recommended: every 90 days)
- Removing team member access
- Compliance requirements
Impact:
- ⚠️ All workflows using this client will lose authentication
- ⚠️ All direct API integrations will fail
- ⚠️ Must update workflows and backend code
Steps:
- Click Regenerate Secret on the client
- Confirm the action
- Save the new secret securely
- Update all workflows using this client:
- Edit each workflow
- Enter new client secret in Basic Info
- Save (regenerates refresh token)
- Update backend integrations with new secret
- Test all integrations
Best Practice: Stagger regeneration
Day 1: Generate new secret
Day 1-7: Update workflows and code gradually
Day 7: Old secret still works (grace period)
Day 8: Revoke old secret
Revoking a Client
When to revoke:
- Client no longer needed
- Security breach
- Decommissioning application
Impact:
- ⚠️ Immediate and permanent
- ⚠️ All workflows using this client stop working
- ⚠️ All API calls fail with 401 Unauthorized
- ⚠️ Cannot be undone
Steps:
- Click Revoke on the client
- Confirm: "Are you sure? This cannot be undone."
- Client status changes to Revoked
- All associated workflows become inactive
Alternative: Set client to Inactive instead (can be reactivated later)
Editing a Client
You can edit:
- ✅ Client name
- ✅ Rate limit
- ✅ Status (Active/Inactive)
You cannot edit:
- ❌ Client ID (immutable)
- ❌ Client Secret (regenerate instead)
- ❌ Environment (create new client instead)
Client Environments
Production vs Sandbox
| Aspect | Production | Sandbox |
|---|---|---|
| Purpose | Live customer verifications | Testing and development |
| Costs Units | ✅ Yes | ❌ No (free) |
| Rate Limit | Configurable (default: 100/min) | Fixed at 10/min |
| Client ID Prefix | nafsi_live_ | nafsi_test_ |
| Secret Prefix | sk_live_ | sk_test_ |
| Data Retention | As per settings (7-365 days) | 7 days maximum |
| SLA | 99.9% uptime | Best effort |
Best Practices
Use separate clients for each environment:
Development:
- Client: "Dev Client" (sandbox)
- Workflows: Test workflows
- Purpose: Local development
Staging:
- Client: "Staging Client" (sandbox)
- Workflows: Pre-production testing
- Purpose: QA and integration tests
Production:
- Client: "Production Client" (production)
- Workflows: Live workflows
- Purpose: Real customer verifications
Benefits:
- Separate billing
- Independent rate limits
- Safe testing without affecting production
- Easy to identify traffic source
Rate Limiting
How It Works
Request arrives → Check client_id → Count requests in last 60 seconds
→ If count < rate_limit: Allow
→ If count >= rate_limit: Return 429 Too Many Requests
Default Limits:
- Production: 100 requests/minute
- Sandbox: 10 requests/minute
Custom Limits:
- Contact support for higher limits
- Enterprise plans: Up to 1000/min
Headers Returned:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 87
X-RateLimit-Reset: 1643723400
Handling Rate Limits
Response when exceeded:
{
"error": {
"code": "rate_limit_exceeded",
"message": "Rate limit of 100 requests/minute exceeded",
"retry_after": 42
}
}
Best Practice (Exponential Backoff):
async function verifyWithRetry(payload, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
const response = await fetch('/api/verify', {
method: 'POST',
body: JSON.stringify(payload)
});
if (response.status === 429) {
const retryAfter = response.headers.get('Retry-After') || (2 ** i);
await sleep(retryAfter * 1000);
continue;
}
return await response.json();
} catch (err) {
if (i === maxRetries - 1) throw err;
}
}
}
Security Best Practices
1. Client Secret Storage
✅ Do:
// Environment variables
const clientSecret = process.env.NAFSI_CLIENT_SECRET;
// Secrets manager
const secret = await secretsManager.getSecret('nafsi/client-secret');
❌ Don't:
// Hardcoded
const clientSecret = 'sk_live_abc123...';
// In frontend
<script>
const secret = 'sk_live_abc123...'; // NEVER DO THIS!
</script>
2. Frontend Integration
✅ Do:
// Use SDK with workflow_id (has refresh token)
Nafsi.init({
clientId: 'public-id-ok-to-expose',
workflowId: 'workflow-id-ok-to-expose'
});
❌ Don't:
// Expose secret in frontend
Nafsi.init({
clientId: 'public-id',
clientSecret: 'sk_live_...' // NEVER!
});
3. Backend Integration
✅ Do:
const response = await fetch('https://api.nafsi.ai/v1/verify', {
method: 'POST',
headers: {
'X-Client-ID': process.env.NAFSI_CLIENT_ID,
'X-Client-Secret': process.env.NAFSI_CLIENT_SECRET
},
body: JSON.stringify(payload)
});
4. Rotation Policy
- Rotate secrets every 90 days
- Document rotation process
- Use automation where possible
- Test after rotation
5. Access Control
- Limit who can access secrets
- Use role-based access control (RBAC)
- Audit secret access
- Revoke access for former team members
Monitoring Client Usage
View Usage Statistics
- Click View Usage on a client
- See metrics:
- Total requests (last 30 days)
- Success rate
- Average response time
- Verifications by workflow
- Units consumed
- Error breakdown
Set Up Alerts
Configure alerts for:
- Rate limit approaching (90% of limit)
- High error rates (> 5%)
- Unusual usage patterns
- Failed authentication attempts
Troubleshooting
Issue: "Invalid client secret" when creating workflow
Cause: Wrong secret or typo
Solution:
- Verify you're using the correct client
- Check for extra spaces when pasting
- Regenerate secret if lost
- Ensure secret matches environment (production vs sandbox)
Issue: 401 Unauthorized for API calls
Cause: Invalid or revoked credentials
Solution:
- Check client status (should be Active)
- Verify you're using correct client_id and secret
- Ensure secret hasn't been regenerated
- Check if client was revoked
Issue: 429 Too Many Requests
Cause: Rate limit exceeded
Solution:
- Implement exponential backoff
- Batch requests where possible
- Request higher rate limit
- Check for request loops in code
Issue: Workflows stopped working after secret regeneration
Cause: Refresh tokens invalidated
Solution:
- Edit each workflow using this client
- Enter new client secret
- Save workflow (regenerates token)
- Test verification
Next Steps
Last Updated: 2026-01-28