Skip to content

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:

PrefixEnvironmentData isolation
kd_live_*ProductionFull access to production identities, credentials, and policies
kd_test_*Test / StagingIsolated test environment — no access to production data
kd_jit_*Just-in-timeTemporary key with automatic expiry, issued via JIT access workflows

Example request

Terminal window
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.

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

  1. The user authenticates via the dashboard login page (email/password or SSO).
  2. NextAuth issues a signed, HTTP-only, secure session cookie.
  3. Subsequent requests from the browser include the cookie automatically.
  4. 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

ScopeLimitWindow
Standard API endpoints100 requestsPer minute
Credential retrieval60 requestsPer minute
Bulk operations20 requestsPer minute
Authentication endpoints10 requestsPer minute

Rate limit headers

Every API response includes rate limit headers so your application can adapt proactively:

HeaderDescription
X-RateLimit-LimitMaximum number of requests allowed in the current window
X-RateLimit-RemainingNumber of requests remaining in the current window
X-RateLimit-ResetUnix timestamp (seconds) when the current window resets

Example response headers

HTTP/1.1 200 OK
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 87
X-RateLimit-Reset: 1711036800
Content-Type: application/json

Handling 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

  1. The API key or session cookie identifies the authenticated user.
  2. The user’s organizationId is extracted and set as the database session context.
  3. RLS policies on every table filter all queries to rows matching that organizationId.
  4. 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"
}
}
FieldTypeDescription
errorstringA concise, human-readable error message
detailsobjectOptional structured metadata providing additional context about the error

HTTP status codes

VeraID uses standard HTTP status codes consistently across all endpoints:

StatusMeaningWhen it occurs
200 OKSuccessThe request completed successfully
201 CreatedResource createdA new identity, credential, or policy was created
400 Bad RequestValidation errorThe request body is malformed or fails schema validation
401 UnauthorizedAuthentication failedMissing, invalid, or expired API key or session
403 ForbiddenAuthorization failedThe authenticated identity lacks permission for this action
404 Not FoundResource not foundThe requested resource does not exist or is not visible to your organization
429 Too Many RequestsRate limitedYou have exceeded the rate limit for this endpoint
500 Internal Server ErrorServer errorAn 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();