YouVersion PlatformYouVersion Platform
PlatformContent LibraryDev 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
    Display Bible HTMLLanguage rangesSign-in APIsSearch APIsVerse ImagesUSFM 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)
stateUnique random string for CSRF protection, required exactly once. Maximum 256 bytes after URL decoding and UTF-8 encoding. Save the exact value to verify it on every callback.
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

The state limit counts UTF-8 bytes, not characters or the length of the encoded URL. For example, 128 é characters occupy 256 bytes. The server preserves accepted state exactly. A missing, repeated, or oversized state returns 400 invalid_request before a sign-in session is created.

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). Decoding a JWT does not verify it. Use the verified id_token for user identity and the access_token as the bearer credential for API requests.

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(); // Validate tokens.id_token before trusting user identity (see below). // Send tokens.access_token as the bearer credential for API requests. 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 claims after the token exchange (see below).

Post-auth: Verify the ID token and read user info

After Auth Call 3, validate the id_token before using its claims to identify the user. The access_token authorizes API requests; it is not a substitute for the ID token.

Choose a JWT verification library

Use a JWT library that supports signature and claim validation 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 production values when validating the id_token:

FieldValue
issuer (iss)https://api.youversion.com/auth/token
audience (aud)Your app's app_key (the OAuth client_id)
jwks_urihttps://api.youversion.com/.well-known/jwks.json
OIDC discovery URLhttps://api.youversion.com/.well-known/openid-configuration

Validate the ID token

Configure your library to perform OpenID Connect ID-token validation:

  1. Require RS256 and verify the signature with the public key selected by the token header's kid from the trusted JWKS endpoint above.
  2. Require iss to exactly match https://api.youversion.com/auth/token, including the path.
  3. Require aud to contain your app's app_key (the OAuth client_id). If multiple audiences or an azp claim are present, apply the additional OpenID Connect authorized-party checks.
  4. Check exp and reject expired tokens. Validate iat according to your application's token-age policy.
  5. Require nonce to match the value you generated and stored for this sign-in request.

Reject the ID token if validation fails. Do not disable issuer or audience validation to make a token pass.

ID token claims

The id_token carries user identity claims. Profile and email fields depend on the granted scopes. This illustrative payload uses synthetic values; the timestamps are examples, not a valid token:

Code
{ "sub": "3f396bde-8efe-4bf2-95fa-f6afb4cb2", "aud": "YOUR_APP_KEY", "name": "Example User", "iss": "https://api.youversion.com/auth/token", "yvp_id": "3f396bde-8efe-4bf2-95fa-f6afb4cb2", "profile_picture": "", "exp": 1762916965, "nonce": "e9a2905d-4a99-4820-ae3a-b625f45a7983", "iat": 1762913365, "email": "user@example.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.

Access token usage and claims

Send the access_token to the YouVersion API as Authorization: Bearer {access_token}. The API validates the bearer credential. Use the verified ID token, rather than decoded access-token claims, to establish the user's identity in your app.

Current access tokens have an at+JWT header type, use RS256, and do not include a kid header or an aud claim. They include client_id, which identifies the requesting app but is not an audience claim. Do not apply the ID token's app-key audience rule to the access token, substitute client_id for aud, or disable audience checking in your ID-token verifier.

This illustrative access-token payload uses synthetic values:

Code
{ "sub": "3f396bde-8efe-4bf2-95fa-f6afb4cb2", "scope": "openid profile email", "iss": "https://api.youversion.com/auth/token", "exp": 1762916965, "iat": 1762913365, "jti": "19e1903d-0d9e-bbf2-eda7437fa02f", "client_id": "YOUR_APP_KEY" }

The missing aud and kid explain why a verifier configured for ID tokens can reject an access token even when its signature and expiration checks pass. Treat the access token as the API bearer credential; do not use it as a login token for your own backend.

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

  • Validate ID tokens: Verify the signature, issuer, audience, expiration, and nonce as described above before trusting user identity. Send access tokens only as API bearer credentials.
  • 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 OIDC discovery - Production issuer and endpoint metadata
  • 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 November 13, 2026
Language rangesSearch APIs
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: Verify the ID token and read user info
    • Choose a JWT verification library
    • OIDC metadata (issuer + JWKS)
    • Validate the ID token
    • ID token claims
    • Access token usage and claims
  • Best Practices and Resources
    • Security
    • Token Management
    • User Data
    • Testing and Production
    • Resources
JSON
JSON
JSON