YouVersion PlatformYouVersion Platform
PlatformBiblesDev Docs
CommunityPartnersSupport

YouVersion Platform

Build applications and integrate with the world's most popular Bible platform.

Platform Products

  • Platform Portal
  • Developer Documentation
  • App Management

Resources

  • Support
  • Press inquiries

Legal

  • Privacy Policy
  • Terms of Use

© 2026 YouVersion. All rights reserved.

  • Overview
  • API Reference
  • SDKs
  • Changelog
<  Back to Platform
Getting Started
    YouVersion Platform OverviewAPI Usage
Guides
    Sign-in APIsUSFM ReferenceError Codes
Useful Links
    YouVersionGitHub
Guides

Sign-in APIs

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 only state (plus an optional granted_permissions). Direct (no-SDK) integrations that read identity from that first callback, or that gated the replay to /auth/callback on those parameters, must instead replay state alone. 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 scope selects OpenID Connect identity claims. The only supported scope values are openid, profile, and email. openid is required. As of the July 2026 change, /auth/authorize rejects any unsupported scope value with a 400 invalid_scope error rather than silently ignoring it.
  • requested_permissions[] requests access to a user's YouVersion data. The only supported permission is highlights. 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.

  1. Create a new App
  2. Set the callback url
  3. Upon creation, note the app_key. This will be the oauth client_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
https://api.youversion.com/auth/authorize

Query Parameters

ParameterDescription
response_typecode
client_idYour app's client ID from the Platform Portal (the app_key)
redirect_uriYour app's callback URL (must match the one registered in the Platform Portal)
scopeSpace-separated list of requested scopes. Supported values: openid, profile, email (openid required). Any other value is rejected with 400 invalid_scope.
nonceRandom string for replay protection (generate a unique value per request)
stateRandom string for CSRF protection (generate a unique value per request)
code_challengeBase64 URL-encoded SHA256 hash of the code_verifier (for PKCE)
code_challenge_methodS256 (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_interactiontrue Optional param for Android and any platform that requires user interaction to continue sign-in flows

Example Request URL

Code
https://api.youversion.com/auth/authorize ?response_type=code &client_id=12345678 &redirect_uri=http://localhost:8001/demo_app.html &scope=openid%20profile%20email &nonce=e9a2905d-4a99-4820-ae3a-b625f45a7983 &state=Bz0u79kz_yczJqJOMTdFog &code_challenge=bxzOnBbgbSJdOS0TIg &code_challenge_method=S256

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.

ParameterDescription
stateThe same state value you provided in the original request (for CSRF validation)
granted_permissionsOptional. 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
http://localhost:8001/demo_app.html ?state=Bz0u79kz_yczJqJOMTdFog

With a granted permission:

Code
http://localhost:8001/demo_app.html ?state=Bz0u79kz_yczJqJOMTdFog &granted_permissions=highlights

Migration note: Before July 20–21, 2026 this callback carried yvp_id, user_name, user_email, and profile_picture. It no longer does. Validate state against the value you generated in Auth Call 1, then replay state alone to /auth/callback as 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
https://api.youversion.com/auth/callback

Query Parameters

ParameterDescription
stateThe same state value from the first callback (identity is resolved server-side from this value)

Example Request URL

TerminalCode
https://api.youversion.com/auth/callback ?state=Bz0u79kz_yczJqJOMTdFog

Response

The server will respond with a redirect (302) to your callback URL with the authorization code:

Code
http://localhost:8001/demo_app.html ?code=YAJuAqhm &state=Bz0u79kz_yczJqJOMTdFog

Response Parameters:

  • code: The authorization code to exchange for tokens
  • state: Your original state value for validation
  • granted_permissions (optional): Comma-separated granted permission keys, present only when requested_permissions[] was included in Auth Call 1

Note: No scope parameter 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
POST https://api.youversion.com/auth/token

Request Body Parameters

ParameterDescription
grant_typeauthorization_code (OAuth 2.0 grant type)
codeThe authorization code returned by Auth Call 2
redirect_uriMust match the redirect_uri sent to Auth Call 1
client_idYour app's client ID (the app_key)
code_verifierThe original PKCE code verifier (before hashing for code_challenge)

Example Request

TerminalCode
curl 'https://api.youversion.com/auth/token' \ --data-raw 'grant_type=authorization_code &code=YAJuAqhm &redirect_uri=http%3A%2F%2Flocalhost%3A8001%2Fdemo_app.html &client_id=12345678 &code_verifier=ZN153cyjXzx9BA0'

Response

The server responds with a JSON object containing the access tokens:

Code
{ "access_token": "eyJ...", "token_type": "Bearer", "expires_in": "3599", "refresh_token": "XDO52GWc739", "id_token": "eyJraWQg...", "scope": "openid" }

Response Fields:

  • access_token: The OAuth 2.0 access token (JWT - use in API requests with Authorization: Bearer header)
  • token_type: Always Bearer
  • expires_in: Token lifetime in seconds (typically 3599 = ~1 hour)
  • refresh_token: Token to obtain a new access token when it expires
  • id_token: OpenID Connect ID token (JWT containing user claims like email, name, etc.)
  • scope: The granted scopes

Note: Both access_token and id_token are 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
<!-- Served at your registered redirect_uri, e.g. http://localhost:8001/demo_app.html --> <script> const API_BASE = "https://api.youversion.com"; const CLIENT_ID = "12345678"; // your app_key const REDIRECT_URI = "http://localhost:8001/demo_app.html"; async function handleCallback() { const params = new URLSearchParams(window.location.search); const returnedState = params.get("state"); const code = params.get("code"); const error = params.get("error"); const expectedState = sessionStorage.getItem("oauth_state"); const codeVerifier = sessionStorage.getItem("pkce_code_verifier"); // Validate state on every callback (CSRF protection). The value is present // on the error, first, and code redirects. if (!returnedState || returnedState !== expectedState) { throw new Error("State mismatch — possible CSRF; aborting."); } if (error) { // A denied or failed authorization redirects back with error + // error_description (and no code). Surface it instead of replaying state, // which would otherwise loop on the failure. throw new Error(`Authorization failed: ${error} — ${params.get("error_description") || ""}`); } if (!code) { // First (state-only) callback. Identity is resolved server-side, so replay // state alone via a top-level navigation. /auth/callback 302-redirects back // to this page with ?code=...&state=..., re-entering handleCallback(). window.location.assign( `${API_BASE}/auth/callback?state=${encodeURIComponent(returnedState)}` ); return; } // Code redirect. Exchange the code for tokens with the PKCE verifier // (public client, no secret). if (!codeVerifier) { throw new Error("Missing PKCE code_verifier — cannot complete token exchange."); } const tokenResp = await fetch(`${API_BASE}/auth/token`, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams({ grant_type: "authorization_code", code, redirect_uri: REDIRECT_URI, // must match the redirect_uri from Auth Call 1 client_id: CLIENT_ID, code_verifier: codeVerifier, }), }); if (!tokenResp.ok) { throw new Error(`Token exchange failed: ${tokenResp.status}`); } const tokens = await tokenResp.json(); // tokens.access_token / tokens.id_token are JWTs — verify signatures before use. return tokens; } handleCallback().catch((err) => console.error(err)); </script>

Reminder: Because the first callback no longer carries identity, do not attempt to read yvp_id, user_name, user_email, or profile_picture from the callback URL. Read user identity from the verified id_token / access_token claims 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:

FieldValue
issuer (iss)https://api.youversion.com
audience (aud)Your app's app_key (the OAuth client_id)
jwks_urihttps://api.youversion.com/.well-known/jwks.json
OIDC discovery URLNot available at this time

Important: Always verify the JWT signature using the public keys from https://api.youversion.com/.well-known/jwks.json before trusting any claims.

Security note: Restrict JWT verification to an allow-list of asymmetric algorithms (for example RS256), and always validate iss and aud in 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
{ "sub": "3f396bde-8efe-4bf2-95fa-f6afb4cb2", "aud": "9gHSQB4BiZkSjJUA9OiTsNa5dv0fACPjj4NOzXRZ", "name": "Bob Smith", "iss": "https://api.youversion.com", "yvp_id": "123-456-789", "profile_picture": "https://mypicture/bobsmith.png", "exp": 1762916965, "nonce": "e9a2905d-493", "iat": 1762913365, "email": "bobsmith@AOL.COM", "jti": "19e1903d-0d9e-bbf2-eda7437fa02f" }

Key Claims:

ClaimDescription
yvp_idThe user's unique YouVersion Platform ID - use this as the primary user identifier
subSubject - also contains the user's unique ID (same as yvp_id)
emailThe user's email address
nameThe user's display name
profile_pictureURL to the user's profile picture
audAudience - your app's client ID (validates the token is for your app)
issIssuer - the YouVersion API endpoint that issued the token
expExpiration time (Unix timestamp)
iatIssued at time (Unix timestamp)
nonceThe nonce value from your original request (for replay protection)
jtiJWT ID - unique identifier for this token

Best Practice: Use yvp_id as 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 state matches what you sent to prevent CSRF attacks
  • Use unique values: Generate a new state and nonce for every authorization request
  • Store temporarily: Associate state with 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.json before 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_id as primary key: Always use yvp_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.)
Last modified on August 19, 2026
API UsageUSFM Reference
On this page
  • OAuth scopes vs. requested permissions
  • Pre-auth: Retrieve your app_key
  • Auth Call 1: /authorize
    • Endpoint URL
    • Query Parameters
    • Example Request URL
    • Redirect back to client
    • Example Redirect URL
  • Auth Call 2: /callback
    • Endpoint URL
    • Query Parameters
    • Example Request URL
    • Response
  • Auth Call 3: /token
    • Endpoint URL
    • Request Body Parameters
    • Example Request
    • Response
  • Copy-paste callback handler (two-hop replay + token exchange)
  • Post-auth: Extracting user info from the JWT tokens
    • Decoding JWTs
    • OIDC metadata (issuer + JWKS)
    • Access Token Claims
  • Best Practices and Resources
    • Security
    • Token Management
    • User Data
    • Testing and Production
    • Resources
JSON
JSON