FlexpaFlexpa
Developer PortalGet a DemoTry it yourself

All docs

Consent

  • How it works
  • What you need

Getting Started

  • Generate PKCE
  • Build authorization URL
  • Scopes
  • Redirect
  • Handle callback
  • Error codes
  • Exchange
  • Fetch records

OAuth API

  • GETAuthorization
  • POSTToken
  • GETIntrospect
  • POSTRevoke
  • POSTRevoke with secret key (legacy)

REST API

  • GETConsent
  • GETConsent by ID
  • GETList consents

Discovery API

  • GETOAuth Authorization Server Metadata
  • GETOpenID Configuration
  • GETSMART Configuration
  • GETJWKS

Mobile applications

  • iOS
  • Android
  • React Native

Workflow Ideas

  • Onboarding
  • Integration
  • Campaigns
  • Incentivization

Best practices

  • Explain the benefit
  • Set expectations
  • Present as default
  • Security
  • Polish your flow
  • Allow for multiple links

Migration from v1

    Troubleshooting

      Next steps

        OAuth Reference

        #Consent

        Flexpa Consent enables secure patient authorization for Records retrieval using SMART on FHIR with OAuth 2.0 PKCE.

        Consent can be used in web and mobile apps.

        #How it works

        1. Your app redirects users to Flexpa's authorization endpoint
        2. Users connect their healthcare records from our 3-in-1 network
        3. Flexpa redirects back to your app with an authorization code
        4. You exchange the authorization code for an access token and start making Records requests

        #What you need

        • A pair of API Keys from the Flexpa Portal
        • A redirect URI registered in the Flexpa Portal. This is where users will be redirected after authorization.
        • Optional: The @flexpa/node-sdk package installed in your project
        Flexpa Consent

        #Looking for a demo?

        Try it yourself

        Go through a Patient Access API flow yourself! See exactly what patients will see

        Try it yourself →

        #Getting Started

        The SMART on FHIR authorization flow involves these main steps:

        1. Generate PKCE - Create a code verifier and challenge
        2. Build authorization URL - Construct the URL with your parameters
        3. Redirect - Send the user to Flexpa for authorization
        4. Handle callback - Process the authorization code on redirect
        5. Exchange - Exchange the code for access tokens
        6. Fetch records - Use the access token to retrieve patient data

        Let's detail each of these steps below.

        The examples below show raw HTTP/JavaScript implementations. For convenience helpers, install the Node SDK:

        Install

        npm install @flexpa/node-sdk
        

        #Generate PKCE

        PKCE (Proof Key for Code Exchange, pronounced "pixie") is an extension to OAuth 2.0 (RFC 7636) that prevents authorization code interception attacks. It ensures the application that starts the authorization flow is the same one that finishes it.

        You'll generate two values:

        • Code Verifier: A cryptographically random string (43-128 characters) that you keep secret and store until the callback
        • Code Challenge: A SHA-256 hash of the verifier, base64url-encoded, sent with the authorization request

        The security works because hashing is one-way: even if an attacker intercepts the authorization code and challenge, they cannot reverse the hash to obtain the verifier needed to exchange the code for tokens.

        For web apps, store the verifier in sessionStorage—it survives redirects within the same tab and is automatically cleared when the tab closes.

        Generate PKCE

        # Generate code verifier (43-128 character base64url string)
        CODE_VERIFIER=$(openssl rand -base64 32 | tr -d '=' | tr '/+' '_-')
        
        # Generate code challenge: BASE64URL(SHA256(verifier))
        CODE_CHALLENGE=$(echo -n "$CODE_VERIFIER" | \
          openssl sha256 -binary | base64 | tr -d '=' | tr '/+' '_-')
        
        echo "Code Verifier: $CODE_VERIFIER"
        echo "Code Challenge: $CODE_CHALLENGE"
        

        #Build authorization URL

        Construct the authorization URL with your OAuth parameters.

        Parameters

        client_idstringRequired

        Your publishable key from the Flexpa Portal.

        redirect_uristringRequired

        The URL where users will be redirected after authorization. Must be registered in the Flexpa Portal.

        response_typestringRequired

        Must be code for the authorization code flow.

        code_challengestringRequired

        The PKCE code challenge generated from the code verifier (43 base64url characters).

        code_challenge_methodstringRequired

        Must be S256 (SHA-256).

        scopestringRequired

        Space-separated OAuth scopes. Must include launch/patient. Add offline_access for refresh tokens enabling multiple usage.

        flexpa_external_idstringRequired

        Your application's user identifier for this patient. Used for tracking and correlating authorizations across your system. If omitted, the authorization request is rejected with error=invalid_request and error_description of flexpa_external_id is required.

        statestring

        An opaque value used to maintain state between the request and callback. This value will be returned in the redirect. Recommended for CSRF protection.

        flexpa_endpoint_idstring

        Pre-select a specific endpoint (health plan) for the user. Skips the endpoint selection screen. Get endpoint IDs from the Directory. Cannot be set at the same time as flexpa_ial2_mode.

        flexpa_line_of_businessstring

        The patient's line of business (insurance plan type), to skip the line of business selection screen. Requires flexpa_endpoint_id, because only a health plan has a line of business. Flexpa validates that the endpoint supports the plan type. If it does not, including when the endpoint is a provider, the authorization request is rejected with error=invalid_request and error_description of flexpa_line_of_business is not supported by this endpoint. Patients whose plan has state-specific routing still see the state selection screen.

        EMPLOYERstring

        For employer-sponsored health plans.

        OFF_EXCHANGEstring

        For off-marketplace individual plans.

        MEDICARE_ADVstring

        For Medicare Advantage plans.

        ON_EXCHANGEstring

        For marketplace/ACA plans.

        MEDICAIDstring

        For Medicaid plans.

        flexpa_ial2_modestring

        The only accepted value is "true". When set, starts the IAL2 identity verification flow — patients verify their identity through CLEAR, ID.me, or Persona, and Flexpa searches TEFCA networks automatically. That search returns provider facilities, not health plans. Once it completes, the patient sees the facilities that were found and can add more endpoints — a health plan included — manually via search. Cannot be set at the same time as flexpa_search_mode or flexpa_endpoint_id.

        flexpa_search_modestring

        The only accepted value is "true". Pass this unless you specifically want the IAL2 identity flow. When set, starts directly in the Consent 2.1 search flow, skipping IAL2 identity verification. Patients search by provider, facility, or health plan and can connect multiple sources in a single session. Cannot be set at the same time as flexpa_ial2_mode.

        flexpa_resumestring

        Set to true when starting an authorization for a user who previously exited the consent flow mid-way (for example, after closing an identity-verification tab) so they can pick up where they left off instead of restarting. Omit the parameter to always start a new authorization.

        Resume is backed by a session cookie set when the user enters the consent flow. If that cookie is no longer present, or any request parameter differs from the original (scope, redirect_uri, flexpa_endpoint_id, etc.), Flexpa silently starts a new authorization — there is no error path to handle.

        Demographic hints (search mode only)

        flexpa_hint_first_namestring

        First name hint for the patient. Requires flexpa_search_mode=true.

        flexpa_hint_last_namestring

        Last name hint for the patient. Requires flexpa_search_mode=true.

        flexpa_hint_dobstring

        Date of birth hint for the patient (YYYY-MM-DD format). Requires flexpa_search_mode=true.

        flexpa_default_filterstring

        Opens the search screen already filtered to one kind of organization. Accepted values are PAYER (health plans) and PROVIDER (hospitals, clinics, and other providers). Patients can still clear the filter or switch to the other one. When omitted, the search screen shows popular providers and popular health plans together.

        Send all three hints, with the date of birth in YYYY-MM-DD format, and the patient skips the demographic collection screen. Anything less pre-fills the screen and still shows it — Flexpa needs a name and birth date either way, to anchor the master Patient that links each connection under one consent.

        On the first screen of Flexpa Consent, every patient agrees to Flexpa's Terms and Privacy policies. No authorization parameter skips it.

        A request that sets neither flexpa_search_mode nor flexpa_ial2_mode currently opens Flexpa Link. By January 27, 2027, when Flexpa Link is removed, it will start the Flexpa Consent search flow instead. To move these requests to the updated interface now, pass flexpa_search_mode=true. See the migration guide.

        Build URL

        https://api.flexpa.com/oauth/authorize
          ?client_id=pk_test_...
          &redirect_uri=https://example.com/callback
          &response_type=code
          &code_challenge=$CODE_CHALLENGE
          &code_challenge_method=S256
          &scope=launch/patient
          &flexpa_external_id=usr_1234
        

        #Scopes

        OAuth scopes control what data your application can access and for how long.

        #Supported scopes

        launch/patientstringRequired

        Required scope that indicates patient context will be provided. This is necessary for all Flexpa integrations.

        offline_accessstring

        Optional scope that requests MULTIPLE usage — Flexpa maintains the authorization with the endpoint and syncs fresh patient data on your application's configured sync frequency, without requiring the user to re-authorize.

        A refresh_token is returned in the token response regardless of this scope. What the scope changes is whether Flexpa continues to sync fresh patient data from the endpoint.

        Not all health plans support offline_access. Where an endpoint can't maintain the authorization, the authorization still succeeds and you still receive a refresh_token, but Flexpa won't sync fresh patient data from that endpoint. The data from the initial sync is available for 24 hours; after that, the user re-authorizes for new data. Refreshability is listed per endpoint in the directory.

        #Redirect

        Once you have the authorization URL, redirect the user to begin the consent flow. Users will:

        1. Connect their health records
        2. Consent to share their data with your application
        3. Be redirected back to your redirect_uri with an authorization code

        #Handle callback

        After authorization, Flexpa redirects back to your redirect_uri with query parameters.

        Parse the code parameter from the URL, retrieve the stored code verifier, and exchange them for tokens.

        Success parameters

        codestring

        The authorization code to exchange for an access token. Valid for 30 minutes.

        statestring

        The state parameter you provided (if any). Verify this matches your stored state for CSRF protection.

        Error parameters

        errorstring

        One of the error codes: access_denied or server_error

        error_descriptionstring

        Human-readable error description providing additional detail

        error_uristring

        Link to error code documentation. Included only on terminal/server error redirects (e.g. server_error, or an abandoned/expired session). It is not present when the patient cancels the flow or no matching records are found.

        statestring

        The state parameter you provided (if any). Always returned when present in the original request.

        Always validate the state parameter matches what you stored to prevent CSRF attacks.

        Success

        https://example.com/callback?code=abc123&state=xyz789
        

        Error

        https://example.com/callback
          ?error=access_denied
          &error_description=User+abandoned+consent
          &error_uri=https://flexpa.com/docs/consent%23error-codes
          &state=xyz789
        

        #Error codes

        When consent fails, Flexpa redirects back to your redirect_uri with standardized error parameters aligned with RFC 6749 where applicable.

        Error codes

        access_deniedstring

        The user did not complete authorization. This includes abandoning the flow, closing the modal, navigating away, or the session timing out. The error_description parameter explains the nature of the error:

        • User cancelled the consent flow
        • No records found for this patient — identity was verified but no matching records were found
        server_errorstring

        An internal error prevented authorization. This includes initialization failures, no facilities authorized, or authorization code creation failures.

        All error redirects include error and, when available, error_description (human-readable detail) and state (echoed back when provided in the original request). The error_uri parameter (a link to this section) is included only on server-driven terminal redirects (when the consent workflow reaches an abandoned or expired state, e.g. server_error or a session timeout). Redirects triggered by the patient explicitly leaving the flow — including access_denied with User cancelled the consent flow and the No records found for this patient case — include error and error_description but may omit error_uri.

        The state parameter is always returned on error redirects when it was provided in the original request. Use this to correlate the error with the user's session and for CSRF validation.

        User cancelled

        https://example.com/callback
          ?error=access_denied
          &error_description=User+cancelled+the+consent+flow
          &state=xyz789
        

        No records found

        https://example.com/callback
          ?error=access_denied
          &error_description=No+records+found+for+this+patient
          &state=xyz789
        

        Server error

        https://example.com/callback
          ?error=server_error
          &error_description=No+facilities+...
          &error_uri=...
          &state=xyz789
        

        #Exchange

        Exchange the authorization code for an access token using the token endpoint.

        This request:

        • Validates the authorization code with Flexpa
        • Verifies the PKCE code verifier
        • Returns access and refresh tokens

        See the Token endpoint for full details on request and response fields.

        Exchange

        POST
        /oauth/token
        curl -X POST https://api.flexpa.com/oauth/token \
          -H "Content-Type: application/json" \
          -d '{
            "grant_type": "authorization_code",
            "client_id": "pk_test_...",
            "code": "the-authorization-code",
            "redirect_uri": "https://example.com/callback",
            "code_verifier": "the-code-verifier"
          }'
        

        #Fetch records

        Use your access token to fetch patient data from the FHIR API.

        Use the Patient Access Token returned from the exchange step.

        The $everything operation returns a Bundle containing all available resources for the patient—claims, coverage, conditions, medications, and more.

        See the Records documentation for the full list of supported FHIR resources and operations.

        Request

        GET
        /fhir/Patient/$everything
        curl 'https://api.flexpa.com/fhir/Patient/$everything' \
          -H "Authorization: Bearer $ACCESS_TOKEN"
        

        #OAuth API

        The OAuth API provides endpoints for authorization, token management, and introspection.


        GEThttps://api.flexpa.com/oauth/authorize

        #Authorization

        The authorization endpoint initiates the OAuth 2.0 PKCE flow. See Build authorization URL for implementation details.


        POSThttps://api.flexpa.com/oauth/token

        #Token

        The token endpoint supports three grant types:

        1. authorization_code - For exchanging an authorization code for access tokens (OAuth PKCE flow)
        2. refresh_token - For refreshing Patient Access Tokens
        3. client_credentials - For obtaining Application Access Tokens

        Request headers

        Authorizationstring

        Client identity can be provided in one of two ways:

        • client_id in the request body — for public clients (e.g. mobile apps) that cannot store a secret key. Supported for authorization_code and refresh_token grants.
        • Authorization: Basic header — for confidential clients with a secret key. Required for client_credentials grant, also supported for refresh_token grant. Concatenate your publishable key and secret key (from the Flexpa Portal) with a colon (:), then base64-encode the result.

        You cannot provide both an Authorization header and client_id in the same request.

        Request body

        grant_typestringRequired

        The type of grant used to obtain the access token.

        Valid values:

        • authorization_code - For exchanging an authorization code (PKCE)
        • client_credentials - For Application Access Tokens
        • refresh_token - For refreshing Patient Access Tokens
        codestring

        Required when grant_type is authorization_code.

        The authorization code from the callback.

        redirect_uristring

        Required when grant_type is authorization_code.

        Must match the redirect URI used in the authorization request.

        code_verifierstring

        Required when grant_type is authorization_code.

        The PKCE code verifier used to generate the code challenge.

        client_idstring

        Required when grant_type is authorization_code. Optional for refresh_token as an alternative to Basic Auth for public clients that cannot store a secret key.

        Your publishable key.

        refresh_tokenstring

        Required when grant_type is refresh_token.

        The refresh_token obtained from the exchange step. Once you have used this token to refresh the access_token, it is no longer valid.

        A new refresh_token will be issued upon successful token refresh.

        When using grant_type=refresh_token, this endpoint accepts any patient authorization whose refresh_token is still valid (within refresh_expires_in, default 90 days) and whose consent has not been revoked. This applies to both ONE_TIME and MULTIPLE usage authorizations — refreshability is determined by the validity of the refresh_token, not by the usage value. Request the offline_access scope to create a MULTIPLE usage authorization. See patient authorization usage for more information.

        When using grant_type=client_credentials, the resulting application access token is bound to the mode of the API keys that minted it: live mode keys mint tokens that see live data, test mode keys mint tokens that see test data. REST endpoints scope their results to the token's mode; the FHIR API requires live mode application tokens.

        Response fields

        access_tokenstring

        The access_token to be used to make Flexpa API requests.

        For Patient Access Tokens, this is associated with a specific patient. For Application Access Tokens, this is associated with your application only.

        expires_innumber

        expires_in is the time (in seconds) for which the access_token is valid.

        For Patient Access Tokens: 86400 seconds (24 hours). For Application Access Tokens: 1800 seconds (30 minutes).

        token_typestring

        The type of token, always Bearer.

        refresh_tokenstring

        The refresh_token to be used to refresh the Patient Access Token.

        Returned on every successful authorization_code exchange and on every successful grant_type=refresh_token refresh, regardless of the requested scopes or the usage of the authorization. Refreshing returns a new access_token valid for another 24 hours; it does not itself sync fresh patient data from the endpoint — that requires MULTIPLE usage via the offline_access scope.

        refresh_expires_innumber

        Returned alongside refresh_token for the authorization_code and refresh_token grants.

        The time period for which the refresh_token is valid. You must call this route to refresh the Patient Access Token before this time period elapses otherwise, the patient will need to re-authorize.

        Defaults to 7776000 seconds (90 days).

        Refresh Token Grant Request (Confidential Client)

        POST
        /oauth/token
        PUBLIC_KEY=pk_test...
        SECRET_KEY=sk_test...
        REFRESH_TOKEN=flexpa-refresh-token
        
        # Base64 encode the credentials
        CREDENTIALS=$(echo -n "${PUBLIC_KEY}:${SECRET_KEY}" | base64)
        
        curl -X POST https://api.flexpa.com/oauth/token \
          -H "Authorization: Basic ${CREDENTIALS}" \
          -H "Content-Type: application/json" \
          -d '{
            "grant_type": "refresh_token",
            "refresh_token": "'"${REFRESH_TOKEN}"'"
          }'
        

        Refresh Token Grant Request (Public Client)

        POST
        /oauth/token
        PUBLIC_KEY=pk_test...
        REFRESH_TOKEN=flexpa-refresh-token
        
        curl -X POST https://api.flexpa.com/oauth/token \
          -H "Content-Type: application/json" \
          -d '{
            "grant_type": "refresh_token",
            "refresh_token": "'"${REFRESH_TOKEN}"'",
            "client_id": "'"${PUBLIC_KEY}"'"
          }'
        

        Client Credentials Grant Request

        POST
        /oauth/token
        PUBLIC_KEY=pk_live...
        SECRET_KEY=sk_live...
        
        # Base64 encode the credentials
        CREDENTIALS=$(echo -n "${PUBLIC_KEY}:${SECRET_KEY}" | base64)
        
        curl -X POST https://api.flexpa.com/oauth/token \
          -H "Authorization: Basic ${CREDENTIALS}" \
          -H "Content-Type: application/json" \
          -d '{
            "grant_type": "client_credentials"
          }'
        

        GEThttps://api.flexpa.com/oauth/introspect

        #Introspect

        Returns information about an access token, including the consent and endpoint details.

        The state, usage, endpoint, sync, and timeUntilMaxAuth fields are deprecated and will be removed on February 1, 2027. They describe only the first patient authorization on the consent, so they can be misleading when a consent has more than one, such as a TEFCA consent. Read these fields per authorization from patientAuthorizations in the Consent API instead.

        Request headers

        AuthorizationstringRequired

        An Authorization: Bearer header value must be presented with a Patient Access Token

        Response fields

        jtistring

        A random nonce value that uniquely identifies the access token.

        iatnumber

        When the access token was issued in Unix time (in seconds).

        expnumber

        When the access token expires in Unix time (in seconds).

        activeboolean

        A boolean that states whether the access token can be used.

        substring

        A unique identifier for the consent backing this access token.

        patientstring

        The Patient ID connected to the authorization (formatted as Patient/<patient_id>).

        endpointobject

        Deprecated. Use patientAuthorizations[].endpoint from the Consent API. The Endpoint to which the patient authorized access, can be used to determine which health plan the patient connected. For some applications this object also includes resources, the FHIR resource types the endpoint supports. The Consent API's endpoint does not include resources; look up the endpoint by its id in the Endpoints API instead.

        usageenum

        Deprecated. Use patientAuthorizations[].usage from the Consent API. Either ONE_TIME or MULTIPLE, indicating the data access pattern.

        statestring

        Deprecated. Use patientAuthorizations[].state from the Consent API. The lifecycle state of the first patient authorization on the consent. One of CREATED, AUTHORIZING, AUTHORIZED, EXCHANGED, ERRORED, ABANDONED, REVOKED, BOUNCED, or EXPIRED.

        client_idstring

        The identifier of the application (client) that the access token was issued to.

        userobject

        An object describing the end user, containing externalId — the external user identifier you supplied when launching the flow (if any).

        syncobject

        Deprecated. Use patientAuthorizations[].sync from the Consent API. Data synchronization status, containing resourceTypes (a map of FHIR resource type to the number of records loaded), lastSyncedAt (when the most recent sync completed, in Unix seconds, or null), and state (the state of the most recent sync job).

        timeUntilMaxAuthnumber | string

        Deprecated. Use patientAuthorizations[].timeUntilMaxAuth from the Consent API. Seconds remaining until the max authorization period expires, for OAUTH authorizations on refreshable endpoints (may be negative once past the window). Otherwise one of: "unsupported" (e.g., IAL1/IAL2/CREDENTIALS types or non-refreshable endpoints), "unknown" (endpoint max auth period not known), or "indefinite" (no expiry). Flexpa calculates this value from the endpoint's maxAuthPeriod and the time the patient authorized. It does not reflect a shorter access duration that the patient selects while authorizing (for example, in Epic MyChart).

        Request

        GET
        /oauth/introspect
        ACCESS_TOKEN=your-access-token
        
        curl https://api.flexpa.com/oauth/introspect \
          -H "Authorization: Bearer $ACCESS_TOKEN"
        

        Response

        {
          "jti": "4e99f5ae-eb40-4161-9506-23b119e7136f",
          "iat": 1671116375,
          "exp": 1671202775,
          "active": true,
          "iss": "https://api.flexpa.com",
          "aud": "https://api.flexpa.com/",
          "sub": "329034ef-5fa8-4a08-99d5-c389e22a2533",
          "client_id": "a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
          "patient": "Patient/33512af4-5ab2-4be2-90f5-3945cff01c1a",
          "user": {
            "externalId": "your-external-user-id"
          }
        }
        

        POSThttps://api.flexpa.com/oauth/revoke

        #Revoke

        Revokes a consent and expunges the patient's data from Flexpa's cache. Both the access_token and the refresh_token stop working.

        To revoke only some of the consent's patient authorizations, pass their IDs in patient_authorization_ids. Flexpa revokes those and expunges their data. The consent is not revoked, so its access_token and refresh_token keep working and return data from its other patient authorizations.

        Request body

        tokenstringRequired

        The access_token or refresh_token to revoke

        client_idstringRequired

        Your publishable key

        token_type_hintstring

        access_token or refresh_token. Optional, because Flexpa can tell the two apart.

        patient_authorization_idsstring[]

        The IDs of the patient authorizations to revoke, from patientAuthorizations[].id in the Consent API. List more than one by repeating the parameter, or as an array in a JSON body. You can send it in the request body or the query string, but not both. Omit it to revoke the whole consent.

        Response fields

        countnumber

        The number of patient authorizations revoked by this request

        Every ID in patient_authorization_ids must belong to the token's consent. If any does not, the request returns 400 with invalid_request, names those IDs in error_description, and revokes nothing. Only patient authorizations whose state is AUTHORIZED or EXCHANGED are revoked and included in count. A listed patient authorization in any other state is left unchanged.

        A token that is unknown, expired, already revoked, or issued to another application returns 200 with a count of 0 and revokes nothing. Access tokens expire after 24 hours, so send the refresh_token to revoke a consent reliably. A 503 response means the consent was not revoked; retry the request.

        Request

        POST
        /oauth/revoke
        REFRESH_TOKEN=your-refresh-token
        PUBLIC_KEY=pk_test...
        
        curl -X POST https://api.flexpa.com/oauth/revoke \
          -d "token=${REFRESH_TOKEN}" \
          -d "client_id=${PUBLIC_KEY}"
        

        Response

        {
          "count": 1
        }
        

        POSThttps://api.flexpa.com/oauth/revoke

        #Revoke with secret key (legacy)

        Revokes a consent with your secret key. This request remains for existing integrations, but secret keys are only temporarily supported. New integrations should use Revoke.

        Request headers

        AuthorizationstringRequired

        An Authorization: Bearer header value with the access_token

        Request body

        secret_keystringRequired

        Your Flexpa API secret key

        patient_authorization_idsstring[]

        The patient authorizations to revoke, as in Revoke. You can send it in the request body or the query string, but not both. Omit it to revoke the whole consent.

        Response fields

        countnumber

        The number of patient authorizations revoked by this request

        Request

        POST
        /oauth/revoke
        ACCESS_TOKEN=your-access-token
        
        curl -X POST https://api.flexpa.com/oauth/revoke \
          -H "Authorization: Bearer $ACCESS_TOKEN" \
          -H "Content-Type: application/json" \
          -d '{
            "secret_key": "sk_test..."
          }'
        

        Response

        {
          "count": 1
        }
        

        #REST API


        GEThttps://api.flexpa.com/rest/consent

        #Consent

        Returns the consent for the authenticated patient. The consent ID is inferred from the Patient Access Token — no URL parameter needed.

        Request headers

        AuthorizationstringRequired

        An Authorization: Bearer header containing a Patient Access Token. Presenting an Application Access Token returns 401 Unauthorized.

        Query parameters

        skipResourceCountsboolean

        When set to true, omits sync.resourceTypes from each patient authorization and skips the FHIR resource count lookup. Useful when you only need consent state, sync status, or scope information and want a faster response. Defaults to false.

        Response fields

        idstring

        The unique identifier for the consent record.

        statestring

        The consent state: CREATED, AUTHORIZED, EXCHANGED, ABANDONED, or REVOKED.

        modestring

        The operational mode: TEST or LIVE.

        createdAtstring

        When the consent was created, in ISO 8601 format.

        updatedAtstring | null

        When the consent was last updated, in ISO 8601 format.

        userobject | null

        The application user associated with this consent, set via flexpa_external_id during authorization.

        externalIdstring

        Your application's user identifier, provided as flexpa_external_id during authorization.

        patientAuthorizationsarray

        Patient authorizations associated with this consent.

        idstring

        Unique identifier for the patient authorization.

        typestring

        How the patient connected. OAUTH for SMART on FHIR authorization (health insurers and medical record systems), CREDENTIALS for patient portal authorization, IAL2 for identity verification via nationwide exchanges (TEFCA IAS), or IAL1 for TEFCA search-flow authorizations using self-attested demographics. See network types.

        statestring

        Authorization state: CREATED, AUTHORIZED, EXCHANGED, ERRORED, ABANDONED, REVOKED, AUTHORIZING, BOUNCED, or EXPIRED.

        usagestring

        Data access pattern: ONE_TIME or MULTIPLE.

        createdAtstring

        When the authorization was created (ISO 8601).

        authorizedAtstring | null

        When the patient completed authorization (ISO 8601).

        exchangedAtstring | null

        When the authorization code was exchanged for tokens (ISO 8601).

        revokedAtstring | null

        When the authorization was revoked (ISO 8601).

        expiredAtstring | null

        When the authorization expired (ISO 8601).

        refreshExpiredAtstring | null

        When the endpoint stopped accepting refreshes for this authorization (ISO 8601), such as after Flexpa's refresh retries run out and it sends a refresh_expired webhook. The authorization's state stays EXCHANGED and active stays true. See When an endpoint stops honoring the authorization.

        patientstring | null

        FHIR Patient reference (formatted as Patient/<id>).

        identityProviderstring | null

        Identity verification provider used for IAL2 authorizations: CLEAR, IDME, or PERSONA. Only present for IAL2 types.

        endpointobject | null

        The health plan endpoint the patient connected to.

        idstring

        Unique identifier for the endpoint.

        namestring

        Machine-readable endpoint name.

        labelstring[]

        Human-readable display labels for the endpoint.

        refreshableboolean

        Whether this endpoint supports token refresh for MULTIPLE usage.

        activeboolean

        Whether the authorization is currently usable (exchanged, not revoked or expired).

        timeUntilMaxAuthnumber | string

        Seconds remaining until the max authorization period expires, for OAUTH authorizations on refreshable endpoints (may be negative once past the window). Otherwise one of: "unsupported" (e.g., IAL1/IAL2/CREDENTIALS types or non-refreshable endpoints), "unknown" (endpoint max auth period not known), or "indefinite" (no expiry). Flexpa calculates this value from the endpoint's maxAuthPeriod and the time the patient authorized. It does not reflect a shorter access duration that the patient selects while authorizing (for example, in Epic MyChart).

        syncobject

        Current data synchronization status.

        resourceTypesobject

        Map of FHIR resource type names to the count of resources synced (e.g. {"ExplanationOfBenefit": 42}). Omitted when skipResourceCounts=true.

        lastSyncedAtnumber | null

        Unix timestamp (seconds) of the last completed sync.

        statestring

        Sync job state: CREATED, WAITING, ACTIVE, COMPLETED, or FAILED. Omitted when no sync job has run yet.

        scopeobject

        SMART on FHIR scope information. Present when scope data is available.

        requestedstring[]

        Scopes requested during authorization.

        grantedstring[]

        Scopes granted by the endpoint.

        rejectedstring[]

        Scopes that were requested but not granted.

        Request

        GET
        /rest/consent
        ACCESS_TOKEN=your-access-token
        
        curl https://api.flexpa.com/rest/consent \
          -H "Authorization: Bearer $ACCESS_TOKEN"
        

        Response

        {
          "id": "01234567-89ab-cdef-0123-456789abcdef",
          "state": "EXCHANGED",
          "mode": "LIVE",
          "createdAt": "2024-01-15T10:30:00.000Z",
          "updatedAt": null,
          "user": { "externalId": "user-123" },
          "patientAuthorizations": [
            {
              "id": "98765432-10fe-dcba-9876-543210fedcba",
              "type": "IAL2",
              "state": "EXCHANGED",
              "usage": "ONE_TIME",
              "createdAt": "2024-01-15T10:31:00.000Z",
              "authorizedAt": "2024-01-15T10:31:30.000Z",
              "exchangedAt": "2024-01-15T10:32:00.000Z",
              "revokedAt": null,
              "expiredAt": null,
              "refreshExpiredAt": null,
              "patient": "Patient/33512af4-5ab2-4be2-90f5-3945cff01c1a",
              "identityProvider": "CLEAR",
              "endpoint": null,
              "active": true,
              "timeUntilMaxAuth": "unsupported",
              "sync": {
                "resourceTypes": { "Condition": 12, "Patient": 1 },
                "lastSyncedAt": 1705312200,
                "state": "COMPLETED"
              }
            },
            {
              "id": "abcdef01-2345-6789-abcd-ef0123456789",
              "type": "OAUTH",
              "state": "EXCHANGED",
              "usage": "ONE_TIME",
              "createdAt": "2024-01-15T10:33:00.000Z",
              "authorizedAt": "2024-01-15T10:33:30.000Z",
              "exchangedAt": "2024-01-15T10:34:00.000Z",
              "revokedAt": null,
              "expiredAt": null,
              "refreshExpiredAt": null,
              "patient": "Patient/33512af4-5ab2-4be2-90f5-3945cff01c1a",
              "endpoint": {
                "id": "d39433b7-0fbd-4bc2-bdae-fb276799979f",
                "name": "epic-mychart",
                "label": ["Epic MyChart"],
                "refreshable": true
              },
              "active": true,
              "timeUntilMaxAuth": 86400,
              "sync": {
                "resourceTypes": { "ExplanationOfBenefit": 42, "Patient": 1 },
                "lastSyncedAt": 1705312200,
                "state": "COMPLETED"
              },
              "scope": {
                "requested": ["patient/*.read", "offline_access"],
                "granted": ["patient/Patient.read", "patient/Coverage.read"],
                "rejected": ["patient/Observation.read"]
              }
            }
          ]
        }
        

        GEThttps://api.flexpa.com/rest/consent/:consentId

        #Consent by ID

        Returns a specific consent by ID. Application tokens can look up any consent belonging to their application. Patient tokens can access only the consent whose ID matches their own token.

        Request headers

        AuthorizationstringRequired

        An Authorization: Bearer header containing either a Patient Access Token or an Application Access Token.

        Query parameters

        skipResourceCountsboolean

        When set to true, omits sync.resourceTypes from each patient authorization and skips the FHIR resource count lookup. Defaults to false.

        includestring[]

        Optional list of additional fields to include in the response. Supported values: tefcaOutcomes. When included, each IAL2 patient authorization will contain a sync.tefcaOutcomes object with a summary of the most recent TEFCA sync. Omitted by default.

        Response fields

        Returns the same fields as Consent. When ?include=tefcaOutcomes is passed, IAL2 patient authorizations include an additional field under sync:

        tefcaOutcomesobject

        Summary of the most recent TEFCA network query. Only present for IAL2 patient authorizations where at least one sync has completed.

        qhinNetworkstring

        QHIN network queried: COMMONWELL or KNO2.

        patientsFoundnumber
        Number of patient records matched across the network.
        facilitiesQueriednumber
        Total number of facilities queried during discovery.
        documentsRetrievednumber
        Total clinical documents retrieved across all matched facilities.
        failureReasonstring | null

        Error message if the TEFCA query failed, otherwise null.

        fhirFunnelobject

        Counts for facilities with SMART-capable FHIR endpoints that were offered as sign-in options.

        facilitiesPresentednumber
        Number of FHIR sign-in capable facilities shown to the patient.
        facilitiesSignInAttemptednumber
        Number of facilities the patient clicked Sign-In for.
        facilitiesSignedInnumber
        Number of facilities where the patient completed OAuth sign-in.
        completedAtstring
        ISO 8601 timestamp when the TEFCA query completed.

        Request

        GET
        /rest/consent/:consentId
        ACCESS_TOKEN=your-access-token
        CONSENT_ID=your-consent-id
        
        curl "https://api.flexpa.com/rest/consent/$CONSENT_ID?include=tefcaOutcomes" \
          -H "Authorization: Bearer $ACCESS_TOKEN"
        

        Response

        {
          "id": "01234567-89ab-cdef-0123-456789abcdef",
          "state": "EXCHANGED",
          "mode": "LIVE",
          "createdAt": "2024-01-15T10:30:00.000Z",
          "updatedAt": null,
          "user": { "externalId": "user-123" },
          "patientAuthorizations": [
            {
              "id": "98765432-10fe-dcba-9876-543210fedcba",
              "type": "IAL2",
              "state": "EXCHANGED",
              "usage": "ONE_TIME",
              "createdAt": "2024-01-15T10:31:00.000Z",
              "authorizedAt": "2024-01-15T10:31:30.000Z",
              "exchangedAt": "2024-01-15T10:32:00.000Z",
              "revokedAt": null,
              "expiredAt": null,
              "refreshExpiredAt": null,
              "patient": "Patient/33512af4-5ab2-4be2-90f5-3945cff01c1a",
              "identityProvider": "CLEAR",
              "endpoint": null,
              "active": true,
              "timeUntilMaxAuth": "unsupported",
              "sync": {
                "resourceTypes": { "Patient": 1, "DocumentReference": 4 },
                "lastSyncedAt": 1705312200,
                "state": "COMPLETED",
                "tefcaOutcomes": {
                  "qhinNetwork": "COMMONWELL",
                  "patientsFound": 7,
                  "facilitiesQueried": 7,
                  "documentsRetrieved": 4,
                  "failureReason": null,
                  "fhirFunnel": {
                    "facilitiesPresented": 3,
                    "facilitiesSignInAttempted": 2,
                    "facilitiesSignedIn": 2
                  },
                  "completedAt": "2026-06-10T16:56:17.000Z"
                }
              }
            }
          ]
        }
        

        GEThttps://api.flexpa.com/rest/consents

        #List consents

        List all consents for your application with cursor-based pagination. Requires an Application Access Token.

        Each consent in the list includes summary patient authorization data (state, endpoint, active status) but omits the detailed sync and scope fields returned by Consent by ID. To get full details for a specific consent, use GET /rest/consent/:consentId.

        Request headers

        AuthorizationstringRequired

        An Authorization: Bearer header value must be presented with an Application Access Token.

        Query parameters

        limitnumber

        Maximum number of consents per page. Defaults to 20, maximum 100.

        cursorstring

        Pagination cursor from a previous response's meta.nextCursor. Omit for the first page.

        statestring

        Filter by consent state: CREATED, AUTHORIZED, EXCHANGED, ABANDONED, or REVOKED.

        Response fields

        consentsarray

        An array of consent objects. Unlike Consent by ID, the patientAuthorizations here are summaries — sync and scope are omitted. Use GET /rest/consent/:consentId for full details.

        idstring

        The unique identifier for the consent record.

        statestring

        The consent state: CREATED, AUTHORIZED, EXCHANGED, ABANDONED, or REVOKED.

        modestring

        The operational mode: TEST or LIVE.

        createdAtstring

        When the consent was created, in ISO 8601 format.

        updatedAtstring | null

        When the consent was last updated, in ISO 8601 format.

        userobject | null

        The application user associated with this consent.

        externalIdstring

        Your application's user identifier, provided as flexpa_external_id during authorization.

        patientAuthorizationsarray

        Summary patient authorizations (no sync or scope).

        idstring

        Unique identifier for the patient authorization.

        typestring

        How the patient connected. OAUTH for SMART on FHIR authorization (health insurers and medical record systems), CREDENTIALS for patient portal authorization, IAL2 for identity verification via nationwide exchanges (TEFCA IAS), or IAL1 for TEFCA search-flow authorizations using self-attested demographics. See network types.

        statestring

        Authorization state: CREATED, AUTHORIZED, EXCHANGED, ERRORED, ABANDONED, REVOKED, AUTHORIZING, BOUNCED, or EXPIRED.

        usagestring

        Data access pattern: ONE_TIME or MULTIPLE.

        createdAtstring

        When the authorization was created (ISO 8601).

        authorizedAtstring | null

        When the patient completed authorization (ISO 8601).

        exchangedAtstring | null

        When the authorization code was exchanged for tokens (ISO 8601).

        revokedAtstring | null

        When the authorization was revoked (ISO 8601).

        expiredAtstring | null

        When the authorization expired (ISO 8601).

        refreshExpiredAtstring | null

        When the endpoint stopped accepting refreshes for this authorization (ISO 8601), such as after Flexpa's refresh retries run out and it sends a refresh_expired webhook. The authorization's state stays EXCHANGED and active stays true. See When an endpoint stops honoring the authorization.

        patientstring | null

        FHIR Patient reference (formatted as Patient/<id>).

        identityProviderstring | null

        Identity verification provider. Only present for IAL2 authorizations: CLEAR, IDME, or PERSONA.

        endpointobject | null

        The health plan endpoint the patient connected to.

        idstring

        Unique identifier for the endpoint.

        namestring

        Machine-readable endpoint name.

        labelstring[]

        Human-readable display labels for the endpoint.

        refreshableboolean

        Whether this endpoint supports token refresh for MULTIPLE usage.

        activeboolean

        Whether the authorization is currently usable (exchanged, not revoked or expired).

        timeUntilMaxAuthnumber | string

        Seconds remaining until the max authorization period expires, for OAUTH authorizations on refreshable endpoints (may be negative once past the window). Otherwise one of: "unsupported" (e.g., IAL1/IAL2/CREDENTIALS types or non-refreshable endpoints), "unknown" (endpoint max auth period not known), or "indefinite" (no expiry). Flexpa calculates this value from the endpoint's maxAuthPeriod and the time the patient authorized. It does not reflect a shorter access duration that the patient selects while authorizing (for example, in Epic MyChart).

        metaobject

        Pagination metadata.

        hasMoreboolean

        Whether more results are available beyond this page.

        nextCursorstring | null

        Cursor to pass as the cursor query parameter for the next page. Null when there are no more results.

        Request

        GET
        /rest/consents
        ACCESS_TOKEN=your-application-access-token
        
        # List all consents
        curl "https://api.flexpa.com/rest/consents?limit=20" \
          -H "Authorization: Bearer $ACCESS_TOKEN"
        
        # Filter by state and mode
        curl "https://api.flexpa.com/rest/consents?state=EXCHANGED" \
          -H "Authorization: Bearer $ACCESS_TOKEN"
        

        Response

        {
          "consents": [
            {
              "id": "01234567-89ab-cdef-0123-456789abcdef",
              "state": "EXCHANGED",
              "mode": "LIVE",
              "createdAt": "2024-01-15T10:30:00.000Z",
              "updatedAt": null,
                  "user": { "externalId": "user-123" },
              "patientAuthorizations": [
                {
                  "id": "98765432-10fe-dcba-9876-543210fedcba",
                  "type": "OAUTH",
                  "state": "EXCHANGED",
                  "usage": "ONE_TIME",
                  "createdAt": "2024-01-15T10:31:00.000Z",
                  "authorizedAt": "2024-01-15T10:31:30.000Z",
                  "exchangedAt": "2024-01-15T10:32:00.000Z",
                  "revokedAt": null,
                  "expiredAt": null,
                  "refreshExpiredAt": null,
                  "patient": "Patient/33512af4-5ab2-4be2-90f5-3945cff01c1a",
                  "endpoint": {
                    "id": "d39433b7-0fbd-4bc2-bdae-fb276799979f",
                    "name": "humana-sandbox",
                    "label": ["Humana"],
                    "refreshable": true
                  },
                  "active": true,
                  "timeUntilMaxAuth": 86400
                }
              ]
            }
          ],
          "meta": {
            "hasMore": true,
            "nextCursor": "eyJjcmVhdGVkQXQiOi..."
          }
        }
        

        #Discovery API

        Standard discovery endpoints for OAuth 2.0 and SMART on FHIR clients to automatically discover authorization server capabilities.

        For automated clients, prefer the discovery metadata for the endpoints and auth methods you use. These values are authoritative and may differ from examples elsewhere in the docs.


        GEThttps://api.flexpa.com/.well-known/oauth-authorization-server

        #OAuth Authorization Server Metadata

        Returns OAuth 2.0 Authorization Server Metadata per RFC 8414. Use this endpoint for standard OAuth 2.0 clients and MCP integrations.

        Response fields

        issuerstring

        The authorization server's issuer identifier.

        authorization_endpointstring

        URL of the authorization endpoint.

        token_endpointstring

        URL of the token endpoint.

        registration_endpointstring

        URL of the dynamic client registration endpoint.

        jwks_uristring

        URL of the JSON Web Key Set.

        response_modes_supportedstring[]

        Supported response modes: query.

        grant_types_supportedstring[]

        Supported grant types: authorization_code, refresh_token, client_credentials.

        code_challenge_methods_supportedstring[]

        Supported PKCE methods: S256.

        token_endpoint_auth_methods_supportedstring[]

        Supported token endpoint authentication methods: client_secret_basic, none.

        scopes_supportedstring[]

        Supported scopes: launch/patient, offline_access.

        Request

        GET
        /.well-known/oauth-authorization-server
        curl https://api.flexpa.com/.well-known/oauth-authorization-server
        

        Response

        {
          "issuer": "https://api.flexpa.com",
          "authorization_endpoint": "https://api.flexpa.com/oauth/authorize",
          "token_endpoint": "https://api.flexpa.com/oauth/token",
          "registration_endpoint": "https://api.flexpa.com/oauth/register",
          "jwks_uri": "https://api.flexpa.com/.well-known/jwks.json",
          "response_types_supported": ["code"],
          "response_modes_supported": ["query"],
          "grant_types_supported": ["authorization_code", "refresh_token", "client_credentials"],
          "code_challenge_methods_supported": ["S256"],
          "token_endpoint_auth_methods_supported": ["client_secret_basic", "none"],
          "scopes_supported": ["launch/patient", "offline_access"]
        }
        

        GEThttps://api.flexpa.com/.well-known/openid-configuration

        #OpenID Configuration

        Returns OpenID Connect Discovery metadata per OpenID Connect Core 1.0. Use this endpoint for OIDC-compatible clients.

        Response fields

        issuerstring

        The authorization server's issuer identifier.

        authorization_endpointstring

        URL of the authorization endpoint.

        token_endpointstring

        URL of the token endpoint.

        registration_endpointstring

        URL of the dynamic client registration endpoint.

        jwks_uristring

        URL of the JSON Web Key Set.

        response_types_supportedstring[]

        Supported response types: code.

        response_modes_supportedstring[]

        Supported response modes: query.

        grant_types_supportedstring[]

        Supported grant types: authorization_code, refresh_token, client_credentials.

        code_challenge_methods_supportedstring[]

        Supported PKCE methods: S256.

        token_endpoint_auth_methods_supportedstring[]

        Supported authentication methods: client_secret_basic, none.

        scopes_supportedstring[]

        Supported scopes: launch/patient, offline_access.

        subject_types_supportedstring[]

        Supported subject types: public.

        id_token_signing_alg_values_supportedstring[]

        Supported ID token signing algorithms: RS256.

        service_documentationstring

        URL to service documentation.

        Request

        GET
        /.well-known/openid-configuration
        curl https://api.flexpa.com/.well-known/openid-configuration
        

        Response

        {
          "issuer": "https://api.flexpa.com",
          "authorization_endpoint": "https://api.flexpa.com/oauth/authorize",
          "token_endpoint": "https://api.flexpa.com/oauth/token",
          "registration_endpoint": "https://api.flexpa.com/oauth/register",
          "jwks_uri": "https://api.flexpa.com/.well-known/jwks.json",
          "response_types_supported": ["code"],
          "response_modes_supported": ["query"],
          "grant_types_supported": ["authorization_code", "refresh_token", "client_credentials"],
          "code_challenge_methods_supported": ["S256"],
          "token_endpoint_auth_methods_supported": ["client_secret_basic", "none"],
          "scopes_supported": ["launch/patient", "offline_access"],
          "subject_types_supported": ["public"],
          "id_token_signing_alg_values_supported": ["RS256"],
          "service_documentation": "https://flexpa.com/docs"
        }
        

        GEThttps://api.flexpa.com/.well-known/smart-configuration

        #SMART Configuration

        Returns SMART App Launch configuration per the SMART App Launch specification. Use this endpoint for SMART on FHIR applications.

        Response fields

        issuerstring

        The authorization server's issuer identifier.

        authorization_endpointstring

        URL of the authorization endpoint.

        token_endpointstring

        URL of the token endpoint.

        jwks_uristring

        URL of the JSON Web Key Set.

        grant_types_supportedstring[]

        Supported grant types: authorization_code, refresh_token, client_credentials.

        response_types_supportedstring[]

        Supported response types: code.

        scopes_supportedstring[]

        Supported scopes: launch/patient, offline_access.

        code_challenge_methods_supportedstring[]

        Supported PKCE methods: S256.

        token_endpoint_auth_methods_supportedstring[]

        Supported authentication methods: client_secret_basic, none.

        capabilitiesstring[]

        SMART capabilities: launch-standalone, client-public, client-confidential-symmetric, context-standalone-patient, permission-offline, permission-patient.

        Request

        GET
        /.well-known/smart-configuration
        curl https://api.flexpa.com/.well-known/smart-configuration
        

        Response

        {
          "issuer": "https://api.flexpa.com",
          "authorization_endpoint": "https://api.flexpa.com/link/auth",
          "token_endpoint": "https://api.flexpa.com/link/token",
          "jwks_uri": "https://api.flexpa.com/.well-known/jwks.json",
          "grant_types_supported": ["authorization_code", "refresh_token", "client_credentials"],
          "response_types_supported": ["code"],
          "scopes_supported": ["launch/patient", "offline_access"],
          "code_challenge_methods_supported": ["S256"],
          "token_endpoint_auth_methods_supported": ["client_secret_basic", "none"],
          "capabilities": [
            "launch-standalone",
            "client-public",
            "client-confidential-symmetric",
            "context-standalone-patient",
            "permission-offline",
            "permission-patient"
          ]
        }
        

        GEThttps://api.flexpa.com/.well-known/jwks.json

        #JWKS

        Returns the JSON Web Key Set per RFC 7517. Use this endpoint to verify token signatures.

        The set contains EC (ES256/ES384) keys used to sign tokens issued by Flexpa API, and may contain an RSA key used to sign the client_assertion JWTs Flexpa sends when authenticating to endpoints that require private_key_jwt authentication (RS256 by default, RS384 where the endpoint requires it). Each key's kid is the RFC 7638 thumbprint of the key.

        Request

        GET
        /.well-known/jwks.json
        curl https://api.flexpa.com/.well-known/jwks.json
        

        #Mobile applications

        Consent is a standard OAuth 2.0 PKCE flow, so it works out of the box with the native authentication session APIs that iOS, Android, and React Native already ship: ASWebAuthenticationSession on iOS and Chrome Custom Tabs on Android. This is the mechanism Apple and Google define for authenticating with a third party, and it is what patients already recognize from other apps they log into.

        Building on the OS-native session, rather than rendering the flow inside your app, is what makes the integration strong:

        • Works out of the box. Consent is built to the OS standards for authentication, so you call the API the platform already gives you. There is no Flexpa SDK to install, no versions to keep in step with our releases, no native module to configure, and no WebView shim to maintain.
        • Password manager autofill. The session runs in the system browser context, so the patient's saved credentials, passkeys, and biometrics work when they sign in with their health plan or provider. Payer login pages are the highest-friction step in the flow, and autofill removes most of that friction.
        • Shared browser session. If the patient is already signed in to their payer in Safari or Chrome, they may not have to sign in again.
        • Credentials stay out of your app. Your app never sees the patient's payer username and password, which keeps you out of scope for handling them.
        • Native return path. The OS hands the authorization code back to your app through your registered URL scheme and closes the session automatically.

        Never render the consent flow in an embedded WebView (WKWebView on iOS or WebView on Android). Embedded WebViews let the host app read the credentials the patient types, break password manager autofill, and are prohibited by OAuth 2.0 security best practices. Many payer login pages also block them.

        The examples below all send flexpa_resume=true. Patients on a phone are the most likely to leave mid-flow, whether they switch to their email app for a verification code or dismiss the session by accident. Resume lets them pick up where they left off on the next attempt instead of starting over. It relies on the consent session cookie, which the native session keeps because it shares cookie storage with the system browser.

        #iOS

        Use ASWebAuthenticationSession (iOS 12+), Apple's native authentication session API. It:

        • Opens a secure system browser context
        • Shares authentication state and autofill with Safari
        • Automatically handles the redirect callback and dismisses itself
        • Prevents credential interception by the host app

        Setup:

        1. Register your redirect URI scheme in Info.plist and in the Flexpa Portal
        2. Generate PKCE parameters before starting the session
        3. Store the code verifier in Keychain or memory
        4. Exchange the authorization code for tokens

        Leave prefersEphemeralWebBrowserSession set to false so the session can reuse the patient's existing Safari cookies and offer saved credentials.

        iOS Implementation

        import AuthenticationServices
        import CryptoKit
        
        class FlexpaAuth: NSObject, ASWebAuthenticationPresentationContextProviding {
          private var codeVerifier: String?
        
          func authenticate() {
            // Generate PKCE parameters
            codeVerifier = generateCodeVerifier()
            let codeChallenge = generateCodeChallenge(codeVerifier!)
        
            // Build authorization URL
            var components = URLComponents(string: "https://api.flexpa.com/oauth/authorize")!
            components.queryItems = [
              URLQueryItem(name: "client_id", value: "pk_test_..."),
              URLQueryItem(name: "redirect_uri", value: "yourapp://callback"),
              URLQueryItem(name: "response_type", value: "code"),
              URLQueryItem(name: "code_challenge", value: codeChallenge),
              URLQueryItem(name: "code_challenge_method", value: "S256"),
              URLQueryItem(name: "scope", value: "launch/patient offline_access"),
              URLQueryItem(name: "flexpa_external_id", value: "usr_1234"),
              URLQueryItem(name: "flexpa_search_mode", value: "true"),
              URLQueryItem(name: "flexpa_resume", value: "true"),
              URLQueryItem(name: "state", value: UUID().uuidString)
            ]
        
            let session = ASWebAuthenticationSession(
              url: components.url!,
              callbackURLScheme: "yourapp"
            ) { [weak self] callbackURL, error in
              guard let url = callbackURL,
                    let code = URLComponents(url: url, resolvingAgainstBaseURL: false)?
                      .queryItems?.first(where: { $0.name == "code" })?.value
              else { return }
        
              self?.exchangeCode(code)
            }
        
            session.presentationContextProvider = self
            session.prefersEphemeralWebBrowserSession = false
            session.start()
          }
        
          func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
            UIApplication.shared.connectedScenes
              .compactMap { $0 as? UIWindowScene }
              .flatMap { $0.windows }
              .first { $0.isKeyWindow }!
          }
        }
        

        #Android

        Use Chrome Custom Tabs, Android's native in-app browser session. It:

        • Opens a Chrome tab within your app's task
        • Shares cookies, saved passwords, and autofill with Chrome
        • Provides a native-feeling UI
        • Prevents credential interception

        Setup:

        1. Add an intent filter for your redirect URI in AndroidManifest.xml, and register the same URI in the Flexpa Portal
        2. Generate PKCE parameters before launching Custom Tabs
        3. Store the code verifier in EncryptedSharedPreferences
        4. Handle the redirect in your callback Activity

        AppAuth for Android wraps this pattern, including PKCE generation and the token exchange. Point its authorizationEndpoint and tokenEndpoint at Flexpa and pass the Flexpa parameters as additional query parameters.

        Android Implementation

        <activity
          android:name=".OAuthCallbackActivity"
          android:exported="true"
          android:launchMode="singleTask">
          <intent-filter>
            <action android:name="android.intent.action.VIEW" />
            <category android:name="android.intent.category.DEFAULT" />
            <category android:name="android.intent.category.BROWSABLE" />
            <data
              android:scheme="yourapp"
              android:host="callback" />
          </intent-filter>
        </activity>
        

        #React Native

        React Native reaches the same native authentication sessions as the platforms above. expo-auth-session calls ASWebAuthenticationSession on iOS and Chrome Custom Tabs on Android, generates the PKCE verifier and challenge, and returns the authorization code to your component. You point it at the Flexpa endpoints the way you would any other OAuth provider, and the patient gets the same password manager autofill and shared browser session a fully native app would give them.

        Setup:

        1. Install expo-auth-session and expo-web-browser
        2. Set a scheme in app.config.js (for example flexpaexample) and register the resulting redirect URI in the Flexpa Portal
        3. Call WebBrowser.maybeCompleteAuthSession() at module scope so the session closes when the app is reopened by the deep link
        4. Exchange the authorization code for tokens, sending client_id in the body since a mobile app is a public client

        Bare React Native apps can use react-native-app-auth instead. It wraps the same two native APIs and takes the same parameters, under the names redirectUrl and additionalParameters.

        makeRedirectUri returns a custom-scheme URI built from the scheme in your app config, such as flexpaexample://. That is the value to register in the Portal, and it is available at runtime as request.redirectUri.

        Store the access and refresh tokens in the Keychain or Keystore, for example with expo-secure-store. Never put them in AsyncStorage, which is unencrypted on disk.

        React Native Implementation

        import * as WebBrowser from 'expo-web-browser';
        import { makeRedirectUri, useAuthRequest, CodeChallengeMethod } from 'expo-auth-session';
        
        // Closes the auth session when the app is reopened by the redirect
        WebBrowser.maybeCompleteAuthSession();
        
        const discovery = {
          authorizationEndpoint: 'https://api.flexpa.com/oauth/authorize',
          tokenEndpoint: 'https://api.flexpa.com/oauth/token',
        };
        
        export function useFlexpaAuth(externalId: string) {
          const redirectUri = makeRedirectUri({ scheme: 'flexpaexample' });
        
          const [request, response, promptAsync] = useAuthRequest(
            {
              clientId: 'pk_test_...',
              responseType: 'code',
              redirectUri,
              scopes: ['launch/patient', 'offline_access'],
              usePKCE: true,
              codeChallengeMethod: CodeChallengeMethod.S256,
              extraParams: {
                flexpa_external_id: externalId,
                flexpa_search_mode: 'true',
                flexpa_resume: 'true',
              },
            },
            discovery
          );
        
          return {
            request,
            response,
            promptAsync,
            redirectUri,
            codeVerifier: request?.codeVerifier,
            isReady: !!request,
          };
        }
        

        #Workflow Ideas

        Here are different techniques for embedding Consent into your application to ensure participation of new users and activate existing users:

        #Onboarding

        Incorporate the consent flow directly into your new user onboarding experience. New users have higher engagement as completion of enrollment is a compelling event to complete the authorization.

        As users sign up and learn to navigate your application, prompt them to link their payer account through Flexpa. This could be presented as an initial setup step, or as a highlighted feature in a tutorial. Make sure to explain the benefits and reassure the user about the security of the process.

        #Integration

        Embed the consent trigger as an activity or option within your existing patient interface. This could be as a button, a banner, or a new tab in a user's profile settings. Explain the value of linking their payer account and gently prompt users to do so.

        #Personalization

        On the user's profile or dashboard, you can include a personalized status or progress bar indicating the completion of their profile setup, including whether they've linked their payer account. This visual cue can motivate users to complete their setup.

        #In-app notifications

        Use notifications in your application to remind users to link their payer accounts. These can be triggered based on certain user behaviors, like logging in or navigating to certain sections. Make sure these reminders are not too intrusive and clearly communicate the value.

        #Campaigns

        Send targeted marketing campaigns to encourage users to link their payer accounts:

        • Email: Craft an informative email explaining the benefits and including a direct link
        • SMS: Send a short, compelling message with a link to your application

        #Incentivization

        Consider offering small incentives or rewards for users who link their payer account. This could be in the form of discounts, access to premium features, or other value-added benefits.


        #Best practices

        Below are some principles we recommend to optimize user conversion and ensure users complete the authorization flow.

        #Explain the benefit

        Your UI should tell the user why they want to use Flexpa and the value they get from linking their payer. For example, linking their payer account might save them time inputting medication data manually or enable more relevant recommendations.

        #Set expectations

        Before starting the flow, explain to the user that they'll be redirected to authorize with their health plan. Explain that they'll need to input their username and password, but also that they can create an account with their payer if they do not have one. Explain what data your app collects and why it's needed.

        #Present as default

        Rather than presenting Flexpa and manual flows as equal alternatives, encourage your customers to use Flexpa through the size, positioning, and color of the consent entry point. You can also use labels such as "Recommended" or "Preferred".

        #Security

        Let customers know that the consent flow is secure and uses industry-standard OAuth 2.0 with PKCE. Explain how patients can revoke access and delete their data through privacy.flexpa.com.

        #Polish your flow

        A consent hosting flow that is aesthetically engaging, polished, and reflects your brand conveys the legitimacy and importance of linking an account. Put your branding on the screens your app shows before and after Flexpa Consent. Flexpa Consent does not support custom brand colors. You can set its default color scheme (light, dark, or system) in your application settings in the Flexpa Portal. Patients can still change it with the theme toggle.

        #Allow for multiple links

        Patients sometimes have multiple payer accounts (primary and secondary insurance, or current and prior insurance). To capture all of this information, allow patients to link multiple accounts to your app.


        #Migration from v1

        Flexpa Link will be removed on January 27, 2027. If you use the FlexpaLink SDK, follow the migration guide to move to Flexpa Consent. The v1 documentation covers the FlexpaLink SDK itself.


        #Troubleshooting

        Invalid code_challenge

        The code_challenge must be a base64url-encoded SHA256 hash (43 characters, no padding). Verify you're using SHA256 hashing and correct base64url encoding.

        Invalid redirect_uri

        The redirect_uri must exactly match a URI registered in the Flexpa Portal including protocol, domain, and path. Check for typos and ensure the URI is registered.

        Redirect URIs are registered per mode. A URI registered in test mode is not valid for an authorization request made with a pk_live_ key, which renders an error page with invalid_redirect_uri and "The redirect_uri is not registered for this application mode." Register your production URI in live mode before you cut over. Live mode web URIs must use HTTPS and cannot be localhost, a loopback address, or a private IP, so a test mode URI such as http://localhost:3000 cannot be reused as-is.

        A mobile app's custom scheme, such as yourapp://callback, is valid in both modes. The HTTPS rule above applies to web URIs, which travel over the network; a custom scheme is handed to your app by the operating system instead. Register the exact value your app receives at runtime, since matching is exact. With expo-auth-session that value is request.redirectUri, and in Expo Go it is an exp:// URL that changes with your machine's address, so use a development build to get the stable yourapp:// scheme.

        Invalid or unknown endpoint_id

        Endpoint IDs are also per mode. A flexpa_endpoint_id that resolves in test mode does not resolve in live mode — look the endpoint up again in live mode rather than hard-coding the test mode value. Unlike the redirect_uri error above, this one is delivered as a redirect to your redirect_uri carrying error=invalid_request, so look for it there rather than on screen.

        flexpa_line_of_business is not supported by this endpoint

        The endpoint named by flexpa_endpoint_id does not support the plan type in flexpa_line_of_business, or it is a provider, which has no plan type. Check the endpoint's supported lines of business in the Directory, or omit flexpa_line_of_business so the patient picks their plan type. This error is also delivered as a redirect to your redirect_uri carrying error=invalid_request.

        Token exchange fails

        Common causes:

        • Authorization code expired (30 minute lifetime)
        • Code already used (single-use only)
        • redirect_uri doesn't match the authorization request
        • client_id (publishable key) doesn't match

        Consent stays AUTHORIZED and never becomes EXCHANGED

        The patient completed the flow, but your application never exchanged the authorization code. Usually the callback did not reach your redirect_uri, or your application lost the code_verifier that it stored before the redirect. The initial sync can still complete, but you do not get an access token or a refresh token. A code cannot be exchanged without its original code_verifier or after it expires, so exchange it as soon as the callback arrives. To find these consents, call List consents with state=AUTHORIZED, then ask those patients to connect again.

        For additional help, contact support@flexpa.com with the X-Request-Id response header from the failed request.


        #Next steps

        Quickstart

        Get started with a cloneable quickstart project

        Try it out →

        Flexpa API

        Use Flexpa API as a unified API to access Explanation of Benefits and more

        Build with Flexpa API →
        Status TwitterGitHub

        © 2026 Flexpa. All rights reserved.

        FHIR® is the registered trademark of Health Level Seven International and its use does not constitute endorsement by HL7.