To use YouVersion sign-in from an app or web page, the easiest way to do so is to use our SDKs instead of using the information on this page. This guide walks through the YouVersion OAuth authorization code flow using PKCE, which can be a challenging task and is intended for developing new SDKs or situations where our existing SDKs don't support your tech stack or feature needs.
Breaking change (July 20–21, 2026): The first callback after consent no longer carries identity parameters (
yvp_id,user_name,user_email,profile_picture). Identity is now bound server-side and the browser-facing callback URL carries onlystate(plus an optionalgranted_permissions). Direct (no-SDK) integrations that read identity from that first callback, or that gated the replay to/auth/callbackon those parameters, must instead replaystatealone. See Auth Call 2: /callback below for the corrected two-hop flow.
OAuth scopes vs. requested permissions
Two different concepts control what your app receives, and they are not interchangeable:
- OAuth
scopeselects OpenID Connect identity claims. The only supported scope values areopenid,profile, andemail.openidis required. As of the July 2026 change,/auth/authorizerejects any unsupported scope value with a400 invalid_scopeerror rather than silently ignoring it. requested_permissions[]requests access to a user's YouVersion data. The only supported permission ishighlights. Notes and bookmarks are not supported and must not be requested.
Never pass a permission key (e.g. highlights) as an OAuth scope, and never pass a scope value as a requested_permissions[] entry.
Pre-auth: Retrieve your app_key
First, create your developer account and register your application at platform.YouVersion.com to obtain an App Key.
- Create a new App
- Set the
callback url - Upon creation, note the
app_key. This will be the oauthclient_id
Auth Call 1: /authorize
This initiates the auth flow for the end user, who will be redirected to login.youversion.com and, after signing in there, be presented with scopes ("YouVersion wants to share your email with App XYZ").
ANDROID DEVS - Android requires in-browser "user interaction" for authorization to complete. You will need to additionally add param require_user_interaction=true to this call, which will prompt the user to click a continuation button after each login.
Endpoint URL
Code
Query Parameters
| Parameter | Description |
|---|---|
response_type | code |
client_id | Your app's client ID from the Platform Portal (the app_key) |
redirect_uri | Your app's callback URL (must match the one registered in the Platform Portal) |
scope | Space-separated list of requested scopes. Supported values: openid, profile, email (openid required). Any other value is rejected with 400 invalid_scope. |
nonce | Random string for replay protection (generate a unique value per request) |
state | Random string for CSRF protection (generate a unique value per request) |
code_challenge | Base64 URL-encoded SHA256 hash of the code_verifier (for PKCE) |
code_challenge_method | S256 (indicates SHA256 hashing) |
requested_permissions[] | Optional. Data-access permissions to request, distinct from scope. Only highlights is supported (repeat the param for each, e.g. requested_permissions[]=highlights). |
require_user_interaction | true Optional param for Android and any platform that requires user interaction to continue sign-in flows |
Example Request URL
Code
Redirect back to client
After the user successfully authenticates and grants consent, they will be redirected (303) back to your App's callback URL. This first callback is state-only — it does not carry any identity parameters. Identity is bound server-side and retrieved during the next hop, so you do not receive (or need) yvp_id, user_name, user_email, or profile_picture here.
| Parameter | Description |
|---|---|
state | The same state value you provided in the original request (for CSRF validation) |
granted_permissions | Optional. Comma-separated list of granted permission keys (e.g. highlights). Present only when your request included requested_permissions[]; may be empty if none were granted. |
Example Redirect URL
Code
With a granted permission:
Code
Migration note: Before July 20–21, 2026 this callback carried
yvp_id,user_name,user_email, andprofile_picture. It no longer does. Validatestateagainst the value you generated in Auth Call 1, then replaystatealone to/auth/callbackas shown below.
Auth Call 2: /callback
After validating state from the first callback, your client app replays that state value alone to the /callback endpoint. The server sources the user's identity from the server-side entry it stored during consent (keyed by state), so no identity parameters are sent or needed. This hop mints the authorization code for the final step.
Endpoint URL
Code
Query Parameters
| Parameter | Description |
|---|---|
state | The same state value from the first callback (identity is resolved server-side from this value) |
Example Request URL
Code
Response
The server will respond with a redirect (302) to your callback URL with the authorization code:
Code
Response Parameters:
code: The authorization code to exchange for tokensstate: Your originalstatevalue for validationgranted_permissions(optional): Comma-separated granted permission keys, present only whenrequested_permissions[]was included in Auth Call 1
Note: No
scopeparameter is returned on this redirect. The granted OAuth scopes are available on the token response (Auth Call 3) and inside the issued tokens.
Auth Call 3: /token
Exchange the authorization code returned by Auth Call 2 for access tokens. This is a POST request made from your backend or from a public client. Public clients use PKCE and do not send a client secret.
Endpoint URL
Code
Request Body Parameters
| Parameter | Description |
|---|---|
grant_type | authorization_code (OAuth 2.0 grant type) |
code | The authorization code returned by Auth Call 2 |
redirect_uri | Must match the redirect_uri sent to Auth Call 1 |
client_id | Your app's client ID (the app_key) |
code_verifier | The original PKCE code verifier (before hashing for code_challenge) |
Example Request
Code
Response
The server responds with a JSON object containing the access tokens:
Code
Response Fields:
access_token: The OAuth 2.0 access token (JWT - use in API requests withAuthorization: Bearerheader)token_type: AlwaysBearerexpires_in: Token lifetime in seconds (typically 3599 = ~1 hour)refresh_token: Token to obtain a new access token when it expiresid_token: OpenID Connect ID token (JWT containing user claims like email, name, etc.)scope: The granted scopes
Note: Both
access_tokenandid_tokenare JSON Web Tokens (JWTs). You can decode them at jwt.io to inspect their claims, but always verify signatures in production.
Copy-paste callback handler (two-hop replay + token exchange)
This minimal, dependency-free example shows the corrected direct (no-SDK) flow for a public client. It handles the state-only first callback, replays state alone to /auth/callback, then exchanges the returned code for tokens at /auth/token using the PKCE code_verifier.
It assumes you stored the state and code_verifier you generated in Auth Call 1 (for example in sessionStorage) so you can validate the returned state and complete PKCE.
Your redirect_uri page is loaded twice: once by the state-only first callback, and again after /auth/callback redirects back with the code. The handler below branches on whether a code is present in the URL. Auth Call 2 is driven by a top-level browser navigation (not fetch), because a browser cannot read the Location header of a redirect made with fetch.
Code
Reminder: Because the first callback no longer carries identity, do not attempt to read
yvp_id,user_name,user_email, orprofile_picturefrom the callback URL. Read user identity from the verifiedid_token/access_tokenclaims after the token exchange (see below).
Post-auth: Extracting user info from the JWT tokens
Once you have the tokens from Auth Call 3, you can decode the JWTs to access user information and claims.
Decoding JWTs
To decode and verify JWTs, use a JWT library for your programming language:
- JavaScript/Node.js:
jsonwebtoken,jose - Python:
PyJWT,python-jose - Ruby:
jwt - Go:
golang-jwt/jwt - Java:
java-jwt,jjwt - .NET:
System.IdentityModel.Tokens.Jwt
OIDC metadata (issuer + JWKS)
Use these values when validating access_token / id_token signatures and claims:
| Field | Value |
|---|---|
issuer (iss) | https://api.youversion.com |
audience (aud) | Your app's app_key (the OAuth client_id) |
jwks_uri | https://api.youversion.com/.well-known/jwks.json |
| OIDC discovery URL | Not available at this time |
Important: Always verify the JWT signature using the public keys from
https://api.youversion.com/.well-known/jwks.jsonbefore trusting any claims.
Security note: Restrict JWT verification to an allow-list of asymmetric algorithms (for example
RS256), and always validateissandaudin addition to verifying the signature.
Access Token Claims
The access_token contains user information and authorization details. When decoded, it will look like this:
Code
Key Claims:
| Claim | Description |
|---|---|
yvp_id | The user's unique YouVersion Platform ID - use this as the primary user identifier |
sub | Subject - also contains the user's unique ID (same as yvp_id) |
email | The user's email address |
name | The user's display name |
profile_picture | URL to the user's profile picture |
aud | Audience - your app's client ID (validates the token is for your app) |
iss | Issuer - the YouVersion API endpoint that issued the token |
exp | Expiration time (Unix timestamp) |
iat | Issued at time (Unix timestamp) |
nonce | The nonce value from your original request (for replay protection) |
jti | JWT ID - unique identifier for this token |
Best Practice: Use
yvp_idas the primary key when storing user information in your database. This ID is stable and unique for each user.
Best Practices and Resources
Security
PKCE Implementation
- Generate secure random values: Use cryptographically secure random generators for
code_verifier(43-128 characters) - Never reuse code verifiers: Generate a new one for each authorization flow
- Store code_verifier securely: Keep it in memory or secure storage until token exchange
State and Nonce
- Always validate state: Verify the returned
statematches what you sent to prevent CSRF attacks - Use unique values: Generate a new
stateandnoncefor every authorization request - Store temporarily: Associate
statewith the user's session and validate on callback
Token Security
- Verify JWT signatures: Always validate tokens using the public keys from
https://api.youversion.com/.well-known/jwks.jsonbefore trusting claims - Store tokens securely: Use secure storage (e.g., HTTP-only cookies, encrypted storage) - never in localStorage for web apps
- Never log tokens: Avoid logging access tokens or refresh tokens in production
Token Management
Access Token Usage
- Include in API requests: Send as
Authorization: Bearer {access_token}header - Check expiration: Tokens typically expire in 1 hour - implement refresh logic before expiration
- Handle 401 errors: When an API returns 401, refresh the token and retry
Refresh Tokens
- Store securely: Refresh tokens are long-lived and sensitive
- Implement refresh flow: Use refresh tokens to obtain new access tokens without re-authenticating the user
- Revoke on logout: Call the token revocation endpoint when users log out
User Data
User Identification
- Use
yvp_idas primary key: Always useyvp_id(not email) as the stable user identifier in your database - Don't assume email uniqueness: Users can change emails, so don't rely on email as a primary key
- Update user info on login: Refresh user profile data (name, email, picture) on each login to stay current
Privacy
- Request minimum scopes: Only request the scopes your app actually needs
- Don't share user data: Never share user data with third parties without explicit consent
Testing and Production
- Test thoroughly: Test the complete flow including error cases (denied consent, expired tokens, network failures)
Error Handling
- Handle user denial: Gracefully handle when users decline to authorize your app
- Implement retry logic: Network requests can fail - implement exponential backoff
- Log errors (not tokens): Log error messages and codes, but never log tokens or sensitive data
Resources
Documentation
- OAuth 2.0 RFC 6749 - OAuth 2.0 specification
- PKCE RFC 7636 - Proof Key for Code Exchange specification
- OpenID Connect - OpenID Connect specification
- YouVersion JWKS - Public keys for verifying
access_token/id_token - JWT.io - JWT decoder and debugger
Libraries
- JavaScript:
@auth0/auth0-spa-js,oidc-client-js - Python:
authlib,python-jose - Ruby:
omniauth-oauth2 - Go:
golang.org/x/oauth2 - Java/Kotlin:
AppAuth-Android - Swift:
AppAuth-iOS(but, using our Swift SDK would be easiest.)