End User Authentication
These endpoints handle the core authentication lifecycle for your application's end users: registration, login, session management, and user profiles.
Authentication Headers
End user auth endpoints use application credentials, not dashboard JWTs:
| Header | Value | Required |
|---|---|---|
X-Application-Key | Your app's public key (za_pub_xxxx) | Always |
X-Application-Secret | Your app's secret key (za_sec_xxxx) | Server-side endpoints |
Authorization | Bearer <access_token> | User-authenticated endpoints |
Content-Type | application/json | POST/PATCH requests |
Server-side endpoints (register, login, refresh, revoke) require both the public key and secret key.
User-authenticated endpoints (profile, sessions) require the public key plus the user's access token. No secret key needed — safe for client-side use.
Password Requirements
Before showing a registration form, fetch the application's password requirements for client-side validation:
curl https://api.zyphr.dev/v1/auth/password-requirements \
-H "X-Application-Key: za_pub_xxxx"
const response = await fetch('https://api.zyphr.dev/v1/auth/password-requirements', {
headers: {
'X-Application-Key': process.env.ZYPHR_APP_PUBLIC_KEY,
},
});
const { data } = await response.json();
// data.requirements — { min_length, require_uppercase, require_lowercase, require_numbers, require_special }
This endpoint only requires the public key — no secret needed. Safe to call from the frontend.
Registration
curl -X POST https://api.zyphr.dev/v1/auth/users/register \
-H "X-Application-Key: za_pub_xxxx" \
-H "X-Application-Secret: za_sec_xxxx" \
-H "Content-Type: application/json" \
-d '{
"email": "jane@example.com",
"password": "SecureP@ss123",
"name": "Jane Doe",
"metadata": { "plan": "pro", "source": "landing-page" }
}'
const response = await fetch('https://api.zyphr.dev/v1/auth/users/register', {
method: 'POST',
headers: {
'X-Application-Key': process.env.ZYPHR_APP_PUBLIC_KEY,
'X-Application-Secret': process.env.ZYPHR_APP_SECRET_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: 'jane@example.com',
password: 'SecureP@ss123',
name: 'Jane Doe',
metadata: { plan: 'pro', source: 'landing-page' },
}),
});
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
email | string | Yes | User's email address |
password | string | Yes | Must meet application's password requirements |
name | string | No | Display name |
metadata | object | No | Arbitrary key-value data attached to the user |
Response (201 Created)
{
"data": {
"user": {
"id": "usr_abc123",
"email": "jane@example.com",
"name": "Jane Doe",
"email_verified": false,
"metadata": { "plan": "pro", "source": "landing-page" },
"created_at": "2025-01-15T10:00:00Z"
},
"tokens": {
"access_token": "eyJhbGci...",
"refresh_token": "zrt_xxxx",
"expires_in": 3600,
"token_type": "Bearer"
}
}
}
Error Responses
| Status | Code | Cause |
|---|---|---|
| 400 | validation_error | Missing or invalid email/password |
| 400 | password_validation_error | Password doesn't meet requirements (includes errors array and requirements) |
| 409 | conflict | Email already registered |
Login
curl -X POST https://api.zyphr.dev/v1/auth/users/login \
-H "X-Application-Key: za_pub_xxxx" \
-H "X-Application-Secret: za_sec_xxxx" \
-H "Content-Type: application/json" \
-d '{
"email": "jane@example.com",
"password": "SecureP@ss123"
}'
const response = await fetch('https://api.zyphr.dev/v1/auth/users/login', {
method: 'POST',
headers: {
'X-Application-Key': process.env.ZYPHR_APP_PUBLIC_KEY,
'X-Application-Secret': process.env.ZYPHR_APP_SECRET_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
email: 'jane@example.com',
password: 'SecureP@ss123',
}),
});
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
email | string | Yes | User's email |
password | string | Yes | User's password |
custom_claims | object | No | Custom data to embed in the JWT (max 4KB). Must be a flat object. |
Standard Response (No MFA)
{
"data": {
"mfa_required": false,
"user": {
"id": "usr_abc123",
"email": "jane@example.com",
"name": "Jane Doe",
"email_verified": true,
"avatar_url": null,
"metadata": { "plan": "pro" },
"last_login_at": "2025-01-15T10:00:00Z",
"mfa_enabled": false
},
"tokens": {
"access_token": "eyJhbGci...",
"refresh_token": "zrt_xxxx",
"expires_in": 3600,
"token_type": "Bearer"
}
}
}
MFA-Required Response
When the user has MFA enabled, login returns a challenge token instead of auth tokens:
{
"data": {
"mfa_required": true,
"user": {
"id": "usr_abc123",
"email": "jane@example.com"
},
"mfa_challenge": {
"token": "mfa_challenge_xxxx",
"expires_at": "2025-01-15T10:05:00Z"
}
},
"meta": {
"message": "MFA verification required. Use POST /v1/auth/mfa/verify with the challenge token."
}
}
See Multi-Factor Authentication for completing the MFA flow.
Error Responses
| Status | Code | Cause |
|---|---|---|
| 400 | validation_error | Missing email/password or invalid custom_claims |
| 401 | unauthorized | Invalid email or password, or account is not active |
| 403 | account_locked | Too many failed attempts. Includes locked_until and attempt_count. |
| 403 | session_limit_exceeded | Max concurrent sessions reached. Includes max_sessions and current_sessions. |
Session Management
Refresh Token
Exchange a refresh token for new access and refresh tokens:
curl -X POST https://api.zyphr.dev/v1/auth/sessions/refresh \
-H "X-Application-Key: za_pub_xxxx" \
-H "X-Application-Secret: za_sec_xxxx" \
-H "Content-Type: application/json" \
-d '{
"refresh_token": "zrt_xxxx",
"custom_claims": { "role": "admin" }
}'
const response = await fetch('https://api.zyphr.dev/v1/auth/sessions/refresh', {
method: 'POST',
headers: {
'X-Application-Key': process.env.ZYPHR_APP_PUBLIC_KEY,
'X-Application-Secret': process.env.ZYPHR_APP_SECRET_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
refresh_token: storedRefreshToken,
}),
});
| Parameter | Type | Required | Description |
|---|---|---|---|
refresh_token | string | Yes | The refresh token from login or previous refresh |
custom_claims | object | No | Update JWT claims. If omitted, preserves existing claims. |
Revoke Session (Logout)
curl -X POST https://api.zyphr.dev/v1/auth/sessions/revoke \
-H "X-Application-Key: za_pub_xxxx" \
-H "X-Application-Secret: za_sec_xxxx" \
-H "Content-Type: application/json" \
-d '{ "refresh_token": "zrt_xxxx" }'
const response = await fetch('https://api.zyphr.dev/v1/auth/sessions/revoke', {
method: 'POST',
headers: {
'X-Application-Key': process.env.ZYPHR_APP_PUBLIC_KEY,
'X-Application-Secret': process.env.ZYPHR_APP_SECRET_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({ refresh_token: storedRefreshToken }),
});
Always returns success (even for invalid tokens) to prevent token enumeration.
Revoke All Sessions
Requires the user's access token. Revokes every active session for the authenticated user:
curl -X POST https://api.zyphr.dev/v1/auth/sessions/revoke-all \
-H "X-Application-Key: za_pub_xxxx" \
-H "X-Application-Secret: za_sec_xxxx" \
-H "Authorization: Bearer ACCESS_TOKEN" \
-H "Content-Type: application/json"
const response = await fetch('https://api.zyphr.dev/v1/auth/sessions/revoke-all', {
method: 'POST',
headers: {
'X-Application-Key': process.env.ZYPHR_APP_PUBLIC_KEY,
'X-Application-Secret': process.env.ZYPHR_APP_SECRET_KEY,
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
});
// response.data.sessions_revoked — number of sessions revoked
List Active Sessions
curl https://api.zyphr.dev/v1/auth/sessions \
-H "X-Application-Key: za_pub_xxxx" \
-H "Authorization: Bearer ACCESS_TOKEN"
const response = await fetch('https://api.zyphr.dev/v1/auth/sessions', {
headers: {
'X-Application-Key': process.env.ZYPHR_APP_PUBLIC_KEY,
'Authorization': `Bearer ${accessToken}`,
},
});
Returns a list of active sessions with device info (user agent, IP, created/last used timestamps).
User Profile
These endpoints use the public key + user access token (no secret key needed).
Get Current User
curl https://api.zyphr.dev/v1/auth/users/me \
-H "X-Application-Key: za_pub_xxxx" \
-H "Authorization: Bearer ACCESS_TOKEN"
const response = await fetch('https://api.zyphr.dev/v1/auth/users/me', {
headers: {
'X-Application-Key': process.env.ZYPHR_APP_PUBLIC_KEY,
'Authorization': `Bearer ${accessToken}`,
},
});
Response
{
"data": {
"user": {
"id": "usr_abc123",
"email": "jane@example.com",
"name": "Jane Doe",
"email_verified": true,
"avatar_url": "https://example.com/avatar.jpg",
"metadata": { "plan": "pro" },
"created_at": "2025-01-15T10:00:00Z",
"last_login_at": "2025-01-20T08:30:00Z"
}
}
}
Update Profile
curl -X PATCH https://api.zyphr.dev/v1/auth/users/me \
-H "X-Application-Key: za_pub_xxxx" \
-H "Authorization: Bearer ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Jane Smith",
"avatar_url": "https://example.com/new-avatar.jpg",
"metadata": { "plan": "enterprise" }
}'
const response = await fetch('https://api.zyphr.dev/v1/auth/users/me', {
method: 'PATCH',
headers: {
'X-Application-Key': process.env.ZYPHR_APP_PUBLIC_KEY,
'Authorization': `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: 'Jane Smith',
metadata: { plan: 'enterprise' },
}),
});
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | No | Updated display name |
avatar_url | string | No | Profile image URL |
metadata | object | No | Replaces existing metadata |
Delete Account (GDPR Self-Service)
Users can delete their own account. This is a soft delete — the account is marked as deleted and all sessions are revoked.
curl -X DELETE https://api.zyphr.dev/v1/auth/users/me \
-H "X-Application-Key: za_pub_xxxx" \
-H "Authorization: Bearer ACCESS_TOKEN"
const response = await fetch('https://api.zyphr.dev/v1/auth/users/me', {
method: 'DELETE',
headers: {
'X-Application-Key': process.env.ZYPHR_APP_PUBLIC_KEY,
'Authorization': `Bearer ${accessToken}`,
},
});
| Status | Code | Cause |
|---|---|---|
| 200 | — | Account deleted successfully |
| 404 | not_found | User not found |
| 410 | gone | Account already deleted |
Endpoint Reference
| Method | Endpoint | Auth | Description |
|---|---|---|---|
GET | /v1/auth/password-requirements | Public key | Get password requirements |
POST | /v1/auth/users/register | App credentials | Register new user |
POST | /v1/auth/users/login | App credentials | Login with email/password |
POST | /v1/auth/sessions/refresh | App credentials | Refresh access token |
POST | /v1/auth/sessions/revoke | App credentials | Revoke session (logout) |
POST | /v1/auth/sessions/revoke-all | App credentials + user token | Revoke all sessions |
GET | /v1/auth/sessions | Public key + user token | List active sessions |
GET | /v1/auth/users/me | Public key + user token | Get current user |
PATCH | /v1/auth/users/me | Public key + user token | Update profile |
DELETE | /v1/auth/users/me | Public key + user token | Delete account |