Skip to main content

End-User Field & Endpoint Matrix

This is the single reference for which end-user attributes Zyphr stores, which endpoint returns each one, and at which credential scope. If you are trying to determine whether a field exists — or where to read it from — start here rather than probing endpoints.

Credential scopes

Every row in the matrix is tagged with the scope that can read (or write) it. There are three distinct credentials in Auth-as-a-Service, and they are not interchangeable:

ScopeCredentialRunsReads
End-user tokenX-Application-Key (za_live_pub_* / za_test_pub_*) + Authorization: Bearer <access_token>Browser / mobile / any clientThe signed-in user's own profile and sign-in state
Application key/secretX-Application-Key (za_live_pub_*) + X-Application-Secret (za_live_sec_*)Server-side onlyAny user in the authenticating application (admin scope)
Dashboard JWTAuthorization: Bearer <dashboard JWT>The Zyphr dashboard UIAccount-scoped management. Not for integrators — this is what our own dashboard uses.
Never ship the secret key to a client

X-Application-Secret and the dashboard JWT are server-side only. Client apps use the publishable key (za_live_pub_* / za_test_pub_*) plus the end user's own access token — never the secret. See Client-side Auth for the credential boundary.

Attribute matrix

Scope uses the abbreviations above: app-key = application key/secret (server-side admin), user-token = end-user token (self).

AttributeEndpoint(s)ScopeRead/Write
idGET /auth/users/:id, GET /auth/users/meapp-key, user-tokenRead
emailGET /auth/users/:id, GET /auth/users/me, PATCH /auth/users/:idapp-key, user-tokenRead; write (app-key PATCH)
nameGET /auth/users/:id, GET /auth/users/meapp-key, user-tokenRead; write (PATCH /auth/users/me, app-key PATCH)
avatar_urlGET /auth/users/:id, GET /auth/users/meapp-key, user-tokenRead; write (PATCH /auth/users/me, app-key PATCH)
statusGET /auth/users/:idapp-keyRead; write (app-key PATCH, active/suspended)
is_anonymousGET /auth/users/:idapp-keyRead
metadataGET /auth/users/:id, GET /auth/users/meapp-key, user-tokenRead; write (PATCH /auth/users/me, app-key PATCH)
email_verifiedGET /auth/users/:id, GET /auth/users/:id/auth-methods, GET /auth/users/meapp-key, user-tokenRead
phone_verifiedGET /auth/users/:id, GET /auth/users/:id/auth-methodsapp-keyRead
mfa_enabledGET /auth/users/:id, GET /auth/users/:id/auth-methodsapp-keyRead
first_activity_atGET /auth/users/:idapp-keyRead
last_login_atGET /auth/users/:id, GET /auth/users/meapp-key, user-tokenRead
created_atGET /auth/users/:id, GET /auth/users/meapp-key, user-tokenRead
updated_atGET /auth/users/:idapp-keyRead
has_passwordGET /auth/users/:id/auth-methodsapp-keyRead
oauth_providersGET /auth/users/:id/auth-methods, GET /auth/oauth/connectionsapp-key; user-token (self)Read
passkeys (count)GET /auth/users/:id/auth-methodsapp-keyRead

Notes

  • GET /auth/users/:id is the application-key-scoped directory lookup (the SDK's getEndUserById). It returns the core profile and verification flags but not has_password, oauth_providers, or the passkey count — those live on the sign-in-methods endpoint below.
  • GET /auth/users/me is the self endpoint (end-user token). It returns the subset of profile fields the user is allowed to see about themselves. It does not return status, is_anonymous, or updated_at.
  • metadata is not trusted for authorization. It is writable by the end user through their own token. For authorization data use trusted custom claims.

Sign-in methods: GET /auth/users/:id/auth-methods

The single, application-key-scoped endpoint that answers "how can this user sign in?" — everything you need to build a sign-in methods settings screen without stitching together several calls. It returns:

{
"data": {
"has_password": true,
"oauth_providers": ["google", "apple"],
"mfa_enabled": false,
"email_verified": true,
"phone_verified": false,
"passkeys": 2
}
}
FieldTypeMeaning
has_passwordbooleanUser has a password credential (false for OAuth-only / passwordless users)
oauth_providersstring[]Distinct linked OAuth providers (e.g. google, apple)
mfa_enabledbooleanMulti-factor authentication is enabled
email_verifiedbooleanEmail address is verified
phone_verifiedbooleanPhone number is verified
passkeysintegerNumber of registered WebAuthn passkeys
curl https://api.zyphr.dev/v1/auth/users/USER_ID/auth-methods \
-H "X-Application-Key: za_live_pub_xxxx" \
-H "X-Application-Secret: za_live_sec_xxxx"
has_password, oauth_providers, and the passkey count are only here

These three are not on GET /auth/users/:id. If you need any of them, call auth-methods. An end user can read their own linked providers via the self-scoped GET /auth/oauth/connections (end-user token) — see OAuth.

Application self-metadata: GET / PATCH /auth/application

Application-secret-scoped read and write of the authenticating application's own metadata — both GET and PATCH require X-Application-Key and X-Application-Secret. GET returns it; PATCH sets the test_recipients allowlist. Both are scoped to the calling application via its credentials — there is no application id in the path or body, so there is no cross-tenant reach.

{
"data": {
"id": "app_abc123",
"name": "My SaaS App",
"environment": "live",
"signing_algorithm": "HS256",
"auth_email_mode": "allowlist",
"test_recipients": ["qa@example.com", "dev@example.com"]
}
}
FieldReadWriteMeaning
idThe application ID
nameThe application's display name
environmentlive or test (from the credentials that authenticated the request); null for legacy application-level keys
signing_algorithmHS256 (default), RS256, or ES256 — the algorithm your end-user tokens are signed with
auth_email_mode✓ (PATCH)unrestricted (send auth emails to everyone) or allowlist (restrict to test_recipients) — see below
test_recipients✓ (PATCH)The auth-email allowlist, used only in allowlist mode — see below
# Read your application's metadata (including the current allowlist)
curl https://api.zyphr.dev/v1/auth/application \
-H "X-Application-Key: za_live_pub_xxxx" \
-H "X-Application-Secret: za_live_sec_xxxx"
Confirming your token signing algorithm

signing_algorithm is the authoritative way to check whether your app issues symmetric (HS256) or asymmetric (RS256/ES256) tokens. Do not infer it from the presence of a JWKS — see Verifying tokens & JWKS.

The auth-email gate: auth_email_mode + test_recipients

The auth-email gate decides who receives auth emails (magic link, email OTP, password reset, verification). It has an explicit mode — the safe value is no longer inferred from whether the allowlist is empty:

  • unrestricted (default) — auth emails go to everyone. test_recipients is ignored. This is the normal production setting.
  • allowlist — only addresses in test_recipients receive auth email. Use this for a test/staging app so a database full of synthetic addresses can't mass- bounce your sending reputation. An EMPTY allowlist in allowlist mode fails closed — it blocks all auth email.
The mode is explicit — don't rely on emptiness

An allowlist is a safety feature, so it's tempting to "leave it on" in production. Don't: allowlist mode with the wrong (or empty) list silently blocks every real password reset. Production should be unrestricted; staging should be allowlist with your QA addresses. Because the mode is now stated explicitly, this is a deliberate choice rather than a side effect of leaving a field blank — but it does mean you must set it, not infer it.

Set it from your own infrastructure config with a single application-secret-scoped PATCH (server-side / CI only — never a client operation):

# Staging: restrict auth email to your QA addresses
curl -X PATCH https://api.zyphr.dev/v1/auth/application \
-H "X-Application-Key: za_live_pub_xxxx" \
-H "X-Application-Secret: za_live_sec_xxxx" \
-H "Content-Type: application/json" \
-d '{ "auth_email_mode": "allowlist", "test_recipients": ["qa@example.com", "dev@example.com"] }'
# Production: send auth email to everyone
curl -X PATCH https://api.zyphr.dev/v1/auth/application \
-H "X-Application-Key: za_live_pub_xxxx" \
-H "X-Application-Secret: za_live_sec_xxxx" \
-H "Content-Type: application/json" \
-d '{ "auth_email_mode": "unrestricted", "test_recipients": [] }'

Notes:

  • Application-SECRET scoped — requires X-Application-Secret, so this is a server-side / CI operation, never a client one.
  • GET and PATCH are symmetric — both auth_email_mode and test_recipients appear in the GET response, so config-management tooling can diff desired vs. actual state.
  • auth_email_mode is optional on PATCH — omit it to change only the list and leave the mode as-is. It must be allowlist or unrestricted; anything else returns 400 validation_error. Same for a malformed email / non-array test_recipients.
  • When a send is suppressed by allowlist mode, it is not silent: a messages row is recorded (status: "suppressed", reason recipient_not_allowlisted) and an email.suppressed webhook fires. The public POST /auth/forgot-password still returns 200 (enumeration-safe), so watch the webhook / the dashboard, not the HTTP response, to confirm delivery in staging.
This gate is for AUTH email specifically — separate from your own allowlist

auth_email_mode / test_recipients gate the auth emails Zyphr sends on your behalf (magic link, OTP, password reset, verification). If you maintain an allowlist on your own sending path, it does not cover auth email — those messages are generated inside Zyphr and never pass through your code. Configure both, or a staging environment can look healthy on your side while Zyphr silently sends (or suppresses) nothing.

Infrastructure-as-code

Commit the mode + allowlist to your deploy pipeline and set them per environment — unrestricted for production, allowlist + your QA addresses for staging. Migrating apps were backfilled to preserve prior behavior (a populated list → allowlist, an empty list → unrestricted), so nothing changed on upgrade; set the mode explicitly going forward.

Required credential by endpoint

Which credential each end-user endpoint requires. Use this to design your client/server split up front instead of discovering it per 401. The three credentials (see Credential scopes above):

  • publishableX-Application-Key only (za_*_pub_*). Safe for browser / mobile.
  • secretX-Application-Key + X-Application-Secret (za_*_sec_*). Server-side only.
  • + end-user token — additionally requires Authorization: Bearer <access_token> (the signed-in user acting on themselves).
Where the secret is required, the operation is server-side by design

The core session flow — login, refresh, and revoke (logout) — is publishable, so a client app can sign in, keep the session alive, and sign out without a backend (this is what the auth-react / auth-react-native SDKs are built on). Login is protected by rate limiting + account lockout + abuse detection, not by the secret. Endpoints still marked secret (register, magic-link, phone, email-OTP send/verify, the user directory, claims) run from your backend. This table is derived from the route middleware and reflects the shipped behavior.

Sessions & tokens

EndpointRequired credential
POST /auth/users/loginpublishable
POST /auth/users/registersecret
POST /auth/sessions/refreshpublishable
POST /auth/sessions/revokepublishable
POST /auth/sessions/revoke-allsecret + end-user token
GET /auth/sessionspublishable + end-user token

Profile (self)

EndpointRequired credential
GET /auth/users/mepublishable + end-user token
PATCH /auth/users/mepublishable + end-user token
DELETE /auth/users/mepublishable + end-user token
POST /auth/users/change-passwordpublishable + end-user token
GET /auth/password-requirementspublishable

Directory & claims (admin, server-side)

EndpointRequired credential
GET /auth/users, GET /auth/users/by-email, GET /auth/users/:idsecret
GET /auth/users/:id/auth-methodssecret
PATCH /auth/users/:id, DELETE /auth/users/:idsecret
POST /auth/users/invitesecret
GET / PUT /auth/users/:id/claimssecret

Passwordless & alternate methods

EndpointRequired credential
POST /auth/magic-link/send, /magic-link/verifysecret
POST /auth/forgot-password, /reset-password, /validate-reset-tokensecret
POST /auth/verify-email/send, /verify-email/resendsecret + end-user token
POST /auth/verify-email/confirmsecret
POST /auth/phone/{register,login}/{send,verify}secret
POST /auth/email-otp/{register,login}/{send,verify}secret
GET /auth/phone/available, /email-otp/availablepublishable
POST /auth/users/anonymous, /game-center, /google-play-gamespublishable
POST /auth/users/convertpublishable + end-user token

OAuth & WebAuthn

EndpointRequired credential
GET /auth/oauth/providers, /oauth/:provider/authorizepublishable
GET /auth/oauth/connections, DELETE /auth/oauth/:providerpublishable + end-user token
GET /auth/oauth/tokens/:provider, POST /oauth/tokens/:provider/refreshsecret
GET /auth/webauthn/credentials, /webauthn/statuspublishable + end-user token
POST /auth/webauthn/registration/*secret + end-user token
POST /auth/webauthn/authentication/*secret

See also