Skip to main content

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

  1. Navigate to API Keys in the sidebar
  2. Click Create New Client
  3. Fill in the details:
FieldDescriptionRequired
Client NameDescriptive name (e.g., "Production iOS App")Yes
EnvironmentProduction or SandboxYes
Rate LimitRequests per minute (default: 100)Yes
  1. 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:

  1. Copy Immediately: Click copy buttons
  2. Store Securely:
    • Environment variables
    • Secrets manager (AWS Secrets Manager, HashiCorp Vault)
    • Password manager
  3. Never Commit to Git: Add to .gitignore
  4. 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:

  1. During workflow creation, you enter client_secret
  2. System validates and generates a refresh_token (JWT)
  3. Token is attached to the workflow
  4. SDK/API uses workflow_id to authenticate
  5. Backend looks up workflow's refresh_token
  6. 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:

ColumnDescription
NameClient name
Client IDPublic identifier (truncated)
EnvironmentProduction or Sandbox
CreatedCreation date
Last UsedLast API call timestamp
Rate LimitRequests per minute
StatusActive / Inactive / Revoked
ActionsEdit / 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:

  1. Click Regenerate Secret on the client
  2. Confirm the action
  3. Save the new secret securely
  4. Update all workflows using this client:
    • Edit each workflow
    • Enter new client secret in Basic Info
    • Save (regenerates refresh token)
  5. Update backend integrations with new secret
  6. 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:

  1. Click Revoke on the client
  2. Confirm: "Are you sure? This cannot be undone."
  3. Client status changes to Revoked
  4. 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

AspectProductionSandbox
PurposeLive customer verificationsTesting and development
Costs Units✅ Yes❌ No (free)
Rate LimitConfigurable (default: 100/min)Fixed at 10/min
Client ID Prefixnafsi_live_nafsi_test_
Secret Prefixsk_live_sk_test_
Data RetentionAs per settings (7-365 days)7 days maximum
SLA99.9% uptimeBest 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

  1. Click View Usage on a client
  2. 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:

  1. Verify you're using the correct client
  2. Check for extra spaces when pasting
  3. Regenerate secret if lost
  4. Ensure secret matches environment (production vs sandbox)

Issue: 401 Unauthorized for API calls

Cause: Invalid or revoked credentials

Solution:

  1. Check client status (should be Active)
  2. Verify you're using correct client_id and secret
  3. Ensure secret hasn't been regenerated
  4. Check if client was revoked

Issue: 429 Too Many Requests

Cause: Rate limit exceeded

Solution:

  1. Implement exponential backoff
  2. Batch requests where possible
  3. Request higher rate limit
  4. Check for request loops in code

Issue: Workflows stopped working after secret regeneration

Cause: Refresh tokens invalidated

Solution:

  1. Edit each workflow using this client
  2. Enter new client secret
  3. Save workflow (regenerates token)
  4. Test verification

Next Steps


Last Updated: 2026-01-28