Guide for native mobile apps: multi-business-account access and the OAuth flow

OAuth Android iOS Mobile API Multi-Business-Account PKCE

On ReceiptRoller, it is common for one user to belong to multiple business accounts. When you want to implement a "business account switcher" UI in a native mobile app (Android / iOS), using the pattern in this guide lets the user log in just once and access the data of all their business accounts.

How it works

Three mechanisms are combined.

  1. A user-scoped access token — the token is not bound to a specific business account (AuthorizedOrganizationId = null, only AuthorizedUserId is set)
  2. GET /api/v1/me/organizations — returns the list of business accounts the user belongs to
  3. Attach ?organizationId= to each API — the server performs a membership check on every call

With this, the user performs the web-browser-based OAuth login just once and can then view and operate on all their business accounts while switching between them.

App registration settings

To use this pattern, turn off the "bind the token to one business account at authorization time" checkbox during app registration. You can set it on the app creation screen in the Developer Dashboard.

  • Left on (the default) → a business account selection dialog is shown on the authorization screen, and the token is bound to the one account the user selects (for typical web apps / server integrations)
  • Off → no account selection is shown on the authorization screen, and a user-scoped token is issued (this guide's target)

On the app list detail screen, apps with the flag off show a "multi-account support" badge.

OAuth authorization flow (PKCE + Chrome Custom Tabs / ASWebAuthenticationSession)

Follow the form recommended by RFC 8252 (OAuth 2.0 for Native Apps) and OAuth 2.1. Avoid an in-app WebView and use the system browser component.

  • Android: androidx.browser.customtabs.CustomTabsIntent
  • iOS: AuthenticationServices.ASWebAuthenticationSession

Steps

  1. Register an OAuth client
    • Create it manually from the Developer Dashboard: Creating a new app
    • Turn off the "bind the token to one business account at authorization time" checkbox
    • Set redirect_uri to a custom scheme (e.g. com.example.rrstore://oauth/callback) or an HTTPS App Links / Universal Links URL
  2. Generate PKCE parameters
    • code_verifier: a random string of 43-128 characters
    • code_challenge: BASE64URL(SHA256(code_verifier))
    • state: a random value for CSRF protection
  3. Open /oauth/authorize in the system browser
    https://receiptroller.io/oauth/authorize
      ?response_type=code
      &client_id={your_client_id}
      &redirect_uri={your_redirect_uri}
      &scope=sales.read sns.content.read sns.audience.read analytics.read store.customers.read store.flyers.read
      &state={state}
      &code_challenge={code_challenge}
      &code_challenge_method=S256

    If the user is not logged in, they are directed to /Identity/Account/Login, and after logging in they return to the app's redirect_uri via a consent screen (which shows only the requested scopes, with no business account selection).

    Note: if you have not turned off "bind the token to one business account at authorization time," an additional dropdown to choose "which business account to access" is shown here. To support multiple accounts, always turn it off.

  4. Exchange the authorization code for an access token
    POST https://receiptroller.io/api/v1/auth/token
    Content-Type: application/x-www-form-urlencoded
    
    grant_type=authorization_code
    &code={code_from_redirect}
    &redirect_uri={your_redirect_uri}
    &client_id={your_client_id}
    &client_secret={your_client_secret}
    &code_verifier={code_verifier}

    The response is snake_case JSON per RFC 6749 §5.1.

    {
      "access_token": "...",
      "refresh_token": "...",
      "token_type": "Bearer",
      "expires_in": 3600,
      "scope": "sales.read sns.content.read sns.audience.read analytics.read store.customers.read store.flyers.read"
    }

Get the business accounts the user belongs to

Use the access token to retrieve the list of business accounts.

GET https://receiptroller.io/api/v1/me/organizations
Authorization: Bearer {access_token}

Response:

{
  "organizations": [
    { "id": "9f0dfdc3-77ac-4bf7-9c9e-a5d1285edd88", "name": "AB", "role": "SuperUser,StoreStaff" },
    { "id": "55a5b9bf-287c-4618-a973-66b67004f292", "name": "モスフードサービス", "role": "Admin" }
  ]
}

Show this response as a "business account switcher" UI in the app. Hold the id of the business account the user selects locally and use it in subsequent API calls.

Pass ?organizationId= to each API

Attach the selected business account ID as a query parameter on every call. The server verifies "whether the calling user is a member of that business account" on every call (to prevent cross-org data leaks).

GET https://receiptroller.io/api/v1/sales/kpis?organizationId={selected_org_id}&storeId={store_id}
Authorization: Bearer {access_token}

List of APIs that support multi-business-account access

As of May 2026, the following endpoint groups support the user-scoped token and the ?organizationId= pattern.

Common

  • GET /api/v1/me/organizations — the list of business accounts the user belongs to

Sales / analytics

  • GET /api/v1/sales/kpis
  • GET /api/v1/sales/trend
  • GET /api/v1/sales/advice
  • GET /api/v1/sales/forecast
  • GET /api/v1/sales/products — products + ABC analysis
  • GET /api/v1/sales/visitors — visitor analysis
  • GET /api/v1/sales/churn — churn risk score
  • GET /api/v1/sales/recommendations — product recommendations
  • GET /api/v1/sales/multi-trend — multi-store comparison

SNS

  • GET /api/v1/sns/accounts / /accounts/{id}
  • GET /api/v1/sns/posts / /posts/{id} / /posts/{id}/analytics
  • GET /api/v1/sns/comments

Customers (CRM)

  • GET /api/v1/store/customers / /customers/{id}
  • POST /api/v1/store/customers (upsert)
  • DELETE /api/v1/store/customers/{id}
  • GET /api/v1/store/customers/{id}/purchases

Flyers

  • GET /api/v1/organizations/{orgId}/stores/{storeId}/flyers
  • GET /api/v1/organizations/{orgId}/stores/{storeId}/flyers/{flyerId}
  • POST / PUT / DELETE / publish / unpublish / expire

The flyer endpoints include {orgId} in the URL path, so attaching ?organizationId= is not needed. The server verifies "whether the user can access the orgId in the URL path" on every call.

Sample: get sales KPIs on Android

// 1. After login → get the business account list with /api/v1/me/organizations
val orgs = api.getMyOrganizations()  // Authorization: Bearer ... is attached
showOrgSwitcher(orgs)

// 2. The user selects a business account
val selectedOrgId = "55a5b9bf-287c-4618-a973-66b67004f292"

// 3. Get the sales KPIs
val kpis = api.getSalesKpis(organizationId = selectedOrgId, storeId = "abc123")
showDashboard(kpis)

// 4. No token re-fetch needed when switching business accounts — just change selectedOrgId and request again
val newOrgId = "9f0dfdc3-77ac-4bf7-9c9e-a5d1285edd88"
val newKpis = api.getSalesKpis(organizationId = newOrgId, storeId = "xyz789")

Security considerations

  • Handling client_secret — for a public client (a mobile app alone), the recommended configuration is token_endpoint_auth_method=none, which authenticates with PKCE only and does not include the client_secret in the app binary.
  • Where to store tokens — use the OS's secure area, such as the Android Keystore / iOS Keychain.
  • Lifetimes — access_token is 1 hour, refresh_token is 30 days. The refresh_token is issued as single-use (rotation scheme).
  • Scope minimization — request only the scopes you need. OAuth scope list
  • Server-side membership check — if you specify an organizationId for a business account the user does not belong to, the API returns 403 Forbidden.

Related articles

Published: 2026-05-26 Updated: 2026-07-05
Tags
API (22) OAuth (15) Android (10) iOS (9) Webhook (6) Troubleshooting (5) api (5) App registration (4) POS Integration (4) Reference (4)