Authentication
VeraID supports two authentication methods depending on your integration context. Programmatic integrations use API key authentication, while the VeraID dashboard uses session cookie authentication via NextAuth.
API key authentication
API keys are the primary authentication method for server-to-server integrations, SDKs, and CI/CD pipelines. Every API request must include a valid key in the Authorization header.
Header format
Authorization: Bearer kd_live_abc123def456ghi789...Key environments
VeraID API keys are prefixed by environment to prevent accidental cross-environment usage:
| Prefix | Environment | Data isolation |
|---|---|---|
kd_live_* | Production | Full access to production identities, credentials, and policies |
kd_test_* | Test / Staging | Isolated test environment — no access to production data |
kd_jit_* | Just-in-time | Temporary key with automatic expiry, issued via JIT access workflows |
Example request
curl -X GET https://app.veraid.io/api/v1/identities \ -H "Authorization: Bearer kd_live_abc123def456ghi789" \ -H "Content-Type: application/json"const response = await fetch('https://app.veraid.io/api/v1/identities', { method: 'GET', headers: { 'Authorization': `Bearer ${process.env.VERAID_API_KEY}`, 'Content-Type': 'application/json', },});
const data = await response.json();Key management best practices
- Never commit keys to source control. Use environment variables or a secrets manager (e.g., AWS Secrets Manager, HashiCorp Vault, GCP Secret Manager).
- Use test keys for development. The
kd_test_*prefix ensures you cannot accidentally modify production data. - Rotate keys regularly. Generate a new key, update your integrations, then revoke the old key from Settings → API Keys in the dashboard.
- Scope keys to the minimum required permissions. When generating a key, select only the scopes your integration needs.
- Monitor key usage. VeraID logs every API call with the key that authenticated it. Review usage in the audit log.
Session cookie authentication
The VeraID dashboard (app.veraid.io) uses NextAuth for session-based authentication. When a user signs in through the web interface, a secure HTTP-only session cookie is set.
How it works
- The user authenticates via the dashboard login page (email/password or SSO).
- NextAuth issues a signed, HTTP-only, secure session cookie.
- Subsequent requests from the browser include the cookie automatically.
- The server validates the session and extracts the user’s identity and organization.
When to use session auth
Session cookies are intended for browser-based access only — specifically the VeraID dashboard and any SPA frontends. For all programmatic integrations, use API key authentication instead.
Rate limiting
All API endpoints are rate-limited to ensure platform stability and fair usage. Rate limits are enforced per API key using Upstash Redis.
Default limits
| Scope | Limit | Window |
|---|---|---|
| Standard API endpoints | 100 requests | Per minute |
| Credential retrieval | 60 requests | Per minute |
| Bulk operations | 20 requests | Per minute |
| Authentication endpoints | 10 requests | Per minute |
Rate limit headers
Every API response includes rate limit headers so your application can adapt proactively:
| Header | Description |
|---|---|
X-RateLimit-Limit | Maximum number of requests allowed in the current window |
X-RateLimit-Remaining | Number of requests remaining in the current window |
X-RateLimit-Reset | Unix timestamp (seconds) when the current window resets |
Example response headers
HTTP/1.1 200 OKX-RateLimit-Limit: 100X-RateLimit-Remaining: 87X-RateLimit-Reset: 1711036800Content-Type: application/jsonHandling rate limits
When you exceed the rate limit, the API returns a 429 Too Many Requests response. Your application should implement exponential backoff:
async function fetchWithRetry(url: string, options: RequestInit, maxRetries = 3) { for (let attempt = 0; attempt <= maxRetries; attempt++) { const response = await fetch(url, options);
if (response.status !== 429) { return response; }
const resetAt = Number(response.headers.get('X-RateLimit-Reset')); const waitMs = Math.max((resetAt * 1000) - Date.now(), 1000); const backoff = Math.min(waitMs, 2 ** attempt * 1000);
await new Promise((resolve) => setTimeout(resolve, backoff)); }
throw new Error('Rate limit exceeded after maximum retries');}Multi-tenancy
Every authenticated API request is automatically scoped to the caller’s organization. This scoping is enforced at the database level using PostgreSQL row-level security (RLS) — it is not possible to access, modify, or even query another organization’s data.
How it works
- The API key or session cookie identifies the authenticated user.
- The user’s
organizationIdis extracted and set as the database session context. - RLS policies on every table filter all queries to rows matching that
organizationId. - This guarantee holds for all operations: reads, writes, updates, and deletes.
Implications for API consumers
- You do not need to include an organization ID in your requests — it is inferred from your authentication credentials.
- If you belong to multiple organizations, generate a separate API key for each organization.
- Cross-organization queries are not supported and will never return data from other tenants.
Error handling
All API errors follow a consistent JSON format, making it straightforward to handle errors programmatically.
Error response format
{ "error": "A human-readable description of what went wrong", "details": { "field": "apiKey", "reason": "expired", "expiredAt": "2026-03-15T00:00:00Z" }}| Field | Type | Description |
|---|---|---|
error | string | A concise, human-readable error message |
details | object | Optional structured metadata providing additional context about the error |
HTTP status codes
VeraID uses standard HTTP status codes consistently across all endpoints:
| Status | Meaning | When it occurs |
|---|---|---|
200 OK | Success | The request completed successfully |
201 Created | Resource created | A new identity, credential, or policy was created |
400 Bad Request | Validation error | The request body is malformed or fails schema validation |
401 Unauthorized | Authentication failed | Missing, invalid, or expired API key or session |
403 Forbidden | Authorization failed | The authenticated identity lacks permission for this action |
404 Not Found | Resource not found | The requested resource does not exist or is not visible to your organization |
429 Too Many Requests | Rate limited | You have exceeded the rate limit for this endpoint |
500 Internal Server Error | Server error | An unexpected error occurred — contact support if this persists |
Error handling example
const response = await fetch('https://app.veraid.io/api/v1/identities/id_123', { headers: { 'Authorization': `Bearer ${process.env.VERAID_API_KEY}`, 'Content-Type': 'application/json', },});
if (!response.ok) { const body = await response.json();
switch (response.status) { case 401: throw new Error(`Authentication failed: ${body.error}`); case 403: throw new Error(`Permission denied: ${body.error}`); case 404: throw new Error(`Identity not found: ${body.error}`); case 429: // Handle rate limiting with retry (see Rate Limiting section) break; default: throw new Error(`API error (${response.status}): ${body.error}`); }}
const identity = await response.json();