Skip to main content

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:

HeaderValueRequired
X-Application-KeyYour app's public key (za_pub_xxxx)Always
X-Application-SecretYour app's secret key (za_sec_xxxx)Server-side endpoints
AuthorizationBearer <access_token>User-authenticated endpoints
Content-Typeapplication/jsonPOST/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"
Node.js
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 }
info

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" }
}'
Node.js
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

ParameterTypeRequiredDescription
emailstringYesUser's email address
passwordstringYesMust meet application's password requirements
namestringNoDisplay name
metadataobjectNoArbitrary 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

StatusCodeCause
400validation_errorMissing or invalid email/password
400password_validation_errorPassword doesn't meet requirements (includes errors array and requirements)
409conflictEmail 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"
}'
Node.js
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

ParameterTypeRequiredDescription
emailstringYesUser's email
passwordstringYesUser's password
custom_claimsobjectNoCustom 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

StatusCodeCause
400validation_errorMissing email/password or invalid custom_claims
401unauthorizedInvalid email or password, or account is not active
403account_lockedToo many failed attempts. Includes locked_until and attempt_count.
403session_limit_exceededMax 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" }
}'
Node.js
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,
}),
});
ParameterTypeRequiredDescription
refresh_tokenstringYesThe refresh token from login or previous refresh
custom_claimsobjectNoUpdate 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" }'
Node.js
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"
Node.js
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"
Node.js
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"
Node.js
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" }
}'
Node.js
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' },
}),
});
ParameterTypeRequiredDescription
namestringNoUpdated display name
avatar_urlstringNoProfile image URL
metadataobjectNoReplaces 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"
Node.js
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}`,
},
});
StatusCodeCause
200Account deleted successfully
404not_foundUser not found
410goneAccount already deleted

Endpoint Reference

MethodEndpointAuthDescription
GET/v1/auth/password-requirementsPublic keyGet password requirements
POST/v1/auth/users/registerApp credentialsRegister new user
POST/v1/auth/users/loginApp credentialsLogin with email/password
POST/v1/auth/sessions/refreshApp credentialsRefresh access token
POST/v1/auth/sessions/revokeApp credentialsRevoke session (logout)
POST/v1/auth/sessions/revoke-allApp credentials + user tokenRevoke all sessions
GET/v1/auth/sessionsPublic key + user tokenList active sessions
GET/v1/auth/users/mePublic key + user tokenGet current user
PATCH/v1/auth/users/mePublic key + user tokenUpdate profile
DELETE/v1/auth/users/mePublic key + user tokenDelete account