API Conventions
Learn common patterns, best practices, and conventions used across all AuthSec APIs.
Common Patterns
Authentication Header
All API requests require a Bearer token in the Authorization header:
Authorization: Bearer <jwt-token>
Example:
curl -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
https://dev.api.authsec.dev/uflow/admin/users
Content Type
All request bodies must be JSON with the appropriate header:
Content-Type: application/json
Tenant Identification
Most endpoints require a tenant_id parameter to specify which tenant's data to access:
Query Parameter:
GET /uflow/admin/users?tenant_id=your-tenant-id
Path Parameter:
GET /clientms/tenants/your-tenant-id/clients
Request Body:
{
"tenant_id": "your-tenant-id",
"username": "[email protected]"
}
Response Format
Success Response
Successful requests return appropriate HTTP status codes with JSON bodies:
{
"id": "resource-id",
"field1": "value1",
"field2": "value2",
"created_at": "2026-02-11T19:56:00Z",
"updated_at": "2026-02-11T19:56:00Z"
}
List/Pagination Response
List endpoints return paginated results:
{
"items": [
{"id": "1", "name": "Item 1"},
{"id": "2", "name": "Item 2"}
],
"total": 42,
"page": 1,
"page_size": 10,
"total_pages": 5
}
Pagination Parameters:
page(integer): Page number (1-indexed)page_size(integer): Items per page (default: 10, max: 100)offset(integer): Alternative to page, number of items to skip
Example:
curl "https://dev.api.authsec.dev/uflow/admin/users?tenant_id=abc&page=2&page_size=20" \
-H "Authorization: Bearer TOKEN"
Error Handling
Error Response Structure
All error responses follow a consistent format:
{
"error": {
"code": "ERROR_CODE",
"message": "Human-readable error description",
"details": {
"field": "Additional context"
}
}
}
HTTP Status Codes
| Code | Meaning | When to Expect |
|---|---|---|
| 200 | OK | Successful GET, PUT, PATCH requests |
| 201 | Created | Successful POST creating a resource |
| 204 | No Content | Successful DELETE requests |
| 400 | Bad Request | Invalid request body or parameters |
| 401 | Unauthorized | Missing or invalid authentication token |
| 403 | Forbidden | Authenticated but lacking permissions |
| 404 | Not Found | Resource doesn't exist |
| 409 | Conflict | Resource already exists (e.g., duplicate username) |
| 422 | Unprocessable Entity | Validation errors |
| 429 | Too Many Requests | Rate limit exceeded |
| 500 | Internal Server Error | Server-side error |
Common Error Codes
| Error Code | HTTP Status | Description |
|---|---|---|
INVALID_CREDENTIALS | 401 | Username/password incorrect |
TOKEN_EXPIRED | 401 | JWT token has expired |
INSUFFICIENT_PERMISSIONS | 403 | User lacks required permissions |
RESOURCE_NOT_FOUND | 404 | Requested resource doesn't exist |
DUPLICATE_RESOURCE | 409 | Resource with same identifier exists |
VALIDATION_ERROR | 422 | Request validation failed |
RATE_LIMIT_EXCEEDED | 429 | Too many requests |
Error Handling Example
Python:
import requests
try:
response = requests.get(
"https://dev.api.authsec.dev/uflow/admin/users/user-123",
headers={"Authorization": f"Bearer {token}"}
)
response.raise_for_status()
user = response.json()
except requests.exceptions.HTTPError as e:
error_data = e.response.json()
if e.response.status_code == 401:
print("Authentication failed - token may be expired")
# Re-authenticate
elif e.response.status_code == 403:
print("Permission denied")
elif e.response.status_code == 404:
print("User not found")
else:
print(f"Error: {error_data['error']['message']}")
TypeScript:
async function fetchUser(userId: string, token: string) {
try {
const response = await fetch(
`https://dev.api.authsec.dev/uflow/admin/users/${userId}`,
{ headers: { 'Authorization': `Bearer ${token}` }}
);
if (!response.ok) {
const error = await response.json();
switch (response.status) {
case 401:
throw new Error('Authentication required');
case 403:
throw new Error('Permission denied');
case 404:
throw new Error('User not found');
default:
throw new Error(error.error.message);
}
}
return await response.json();
} catch (error) {
console.error('API Error:', error);
throw error;
}
}
Rate Limiting
Rate Limit Headers
Responses include rate limit information:
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 987
X-RateLimit-Reset: 1644598800
X-RateLimit-Limit: Maximum requests per windowX-RateLimit-Remaining: Requests remaining in current windowX-RateLimit-Reset: Unix timestamp when limit resets
Handling Rate Limits
Respect the limits:
import time
def make_request_with_retry(url, headers, max_retries=3):
for attempt in range(max_retries):
response = requests.get(url, headers=headers)
if response.status_code == 429:
# Rate limited - wait and retry
reset_time = int(response.headers.get('X-RateLimit-Reset', 0))
wait_seconds = max(reset_time - time.time(), 1)
print(f"Rate limited. Waiting {wait_seconds}s...")
time.sleep(wait_seconds)
continue
return response
raise Exception("Max retries exceeded")
Filtering and Searching
Many list endpoints support filtering:
Query Parameters
# Filter by status
GET /uflow/admin/users?tenant_id=abc&active=true
# Search by username
GET /uflow/admin/users?tenant_id=abc&username=john
# Multiple filters
GET /uflow/admin/users?tenant_id=abc&active=true&role=admin
Common Filter Parameters
| Parameter | Type | Description |
|---|---|---|
search | string | Full-text search across relevant fields |
active | boolean | Filter by active/inactive status |
created_after | ISO8601 date | Resources created after date |
created_before | ISO8601 date | Resources created before date |
sort | string | Sort field (e.g., created_at, -username for desc) |
Timestamps
All timestamps are in ISO 8601 format (UTC):
{
"created_at": "2026-02-11T19:56:00Z",
"updated_at": "2026-02-11T19:56:00Z"
}
Parsing in Code:
Python:
from datetime import datetime
timestamp = "2026-02-11T19:56:00Z"
dt = datetime.fromisoformat(timestamp.replace('Z', '+00:00'))
TypeScript:
const timestamp = "2026-02-11T19:56:00Z";
const date = new Date(timestamp);
Idempotency
Idempotent Operations
Some operations are naturally idempotent:
- GET: Always safe to retry
- PUT: Updates are idempotent
- DELETE: Deleting already-deleted resource returns 404 or 204
Non-Idempotent Operations
POST requests (resource creation) are NOT idempotent:
# This creates TWO users if called twice
POST /uflow/admin/users
{"username": "[email protected]", ...}
Use idempotency keys (if supported):
POST /uflow/admin/users
Idempotency-Key: unique-request-id-123
{"username": "[email protected]", ...}
Versioning
API Versions
Some services use versioned paths:
/clients/v1/tenants/{tenantId}/clients
Deprecation Policy
- Notice Period: 90 days minimum before endpoint removal
- Headers: Deprecated endpoints return warning headers:
X-API-Deprecated: true
X-API-Sunset: 2026-06-01T00:00:00Z
Security Best Practices
1. Secure Token Storage
[!CAUTION] Never store tokens in:
- Local storage (XSS vulnerable)
- URL parameters
- Git repositories
- Client-side JavaScript variables
Do:
- Use secure, httpOnly cookies for web apps
- Use secure keychain/keystore for mobile apps
- Rotate tokens regularly
2. HTTPS Only
[!IMPORTANT] All API requests MUST use HTTPS. HTTP requests will be rejected.
3. Token Expiration
Tokens expire after 1 hour by default. Implement token refresh:
def get_valid_token(current_token, refresh_token):
# Check if token is expired (decode JWT)
if is_token_expired(current_token):
# Refresh the token
return refresh_access_token(refresh_token)
return current_token
4. Validate Input
Always validate user input before sending to API:
function validateEmail(email: string): boolean {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
function validatePassword(password: string): boolean {
// At least 8 chars, 1 uppercase, 1 lowercase, 1 number
return password.length >= 8 &&
/[A-Z]/.test(password) &&
/[a-z]/.test(password) &&
/[0-9]/.test(password);
}
Testing
Development Environment
Base URL: https://dev.api.authsec.dev
Test Credentials
[!NOTE] Request test tenant credentials from your administrator
Sample Test Flow
# 1. Authenticate
TOKEN=$(curl -s -X POST https://dev.api.authsec.dev/uflow/admin/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"[email protected]","password":"TestPass123!"}' \
| jq -r '.access_token')
# 2. Make authenticated request
curl https://dev.api.authsec.dev/uflow/admin/users?tenant_id=test-tenant \
-H "Authorization: Bearer $TOKEN"
SDK Usage
Python SDK
from authsec import AuthSecClient
client = AuthSecClient(
base_url="https://dev.api.authsec.dev",
tenant_id="your-tenant-id"
)
# Authenticate
client.login(username="[email protected]", password="password")
# Make requests
users = client.users.list()
user = client.users.get(user_id="user-123")
TypeScript SDK
import { AuthSecClient } from '@authsec/sdk';
const client = new AuthSecClient({
baseUrl: 'https://dev.api.authsec.dev',
tenantId: 'your-tenant-id'
});
// Authenticate
await client.login('[email protected]', 'password');
// Make requests
const users = await client.users.list();
const user = await client.users.get('user-123');
Next Steps
- Core Authentication - Implement authentication
- User Management - Manage users
- Error Reference - Complete error code list (if available)
Questions? Review the Quickstart Guide for a complete example →