{"templateId":"markdown","sharedDataIds":{"sidebar":"sidebar-sidebars.yaml"},"props":{"metadata":{"markdoc":{"tagList":[]},"type":"markdown"},"seo":{"title":"Partner Authentication","llmstxt":{"hide":false,"sections":[{"title":"Table of contents","includeFiles":["**/*"],"excludeFiles":[]}],"excludeFiles":[]}},"dynamicMarkdocComponents":[],"compilationErrors":[],"ast":{"$$mdtype":"Tag","name":"article","attributes":{},"children":[{"$$mdtype":"Tag","name":"Heading","attributes":{"level":1,"id":"partner-authentication","__idx":0},"children":["Partner Authentication"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Partner Authentication lets your application access the Rho API on behalf of Rho customers using ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["OAuth 2.0"]},"."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Rho uses the standard ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Authorization Code flow with PKCE"]},". The authorization server is ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["https://auth.rho.co"]},"."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["If you are building an internal integration for your own account, you don't need OAuth - use an ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/docs/v1/auth"},"children":["API Access Token"]}," instead."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"getting-access","__idx":1},"children":["Getting access"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["OAuth clients are registered by Rho. To onboard as a partner, email the following details to ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["api-partner-request@rho.co"]},":"]},{"$$mdtype":"Tag","name":"div","attributes":{"className":"md-table-wrapper"},"children":[{"$$mdtype":"Tag","name":"table","attributes":{"className":"md"},"children":[{"$$mdtype":"Tag","name":"thead","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Field"},"children":["Field"]},{"$$mdtype":"Tag","name":"th","attributes":{"data-label":"Notes"},"children":["Notes"]}]}]},{"$$mdtype":"Tag","name":"tbody","attributes":{},"children":[{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Client / app name"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Shown to customers on the consent screen."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Company name"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Your legal entity name."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Logo"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Your app's logo image. Shown on the consent screen."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Redirect URI(s)"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["All URIs you need, production, development, local."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Requested scopes"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["The scopes your app needs, e.g. ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["accounts:read transactions:read offline_access"]}," (see ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/docs/v1/auth#scopes"},"children":["Scopes"]},")."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Privacy policy URI"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Linked from the consent screen."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Terms of Service URI"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Linked from the consent screen."]}]},{"$$mdtype":"Tag","name":"tr","attributes":{},"children":[{"$$mdtype":"Tag","name":"td","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Support / contact email"]}]},{"$$mdtype":"Tag","name":"td","attributes":{},"children":["Used for operational and security notices about your integration."]}]}]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["After review, Rho registers your OAuth client and sends you your ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["client_id"]}," and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["client_secret"]},". Treat the secret like a password: store it in a secret manager and never share it with anyone."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"authorization-flow","__idx":2},"children":["Authorization flow"]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"step-1-redirect-the-customer-to-rho","__idx":3},"children":["Step 1: Redirect the customer to Rho"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Send the customer's browser to the Rho authorization URL:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"GET https://auth.rho.co/oauth2/auth?\n  response_type=code&\n  client_id=<your_client_id>&\n  redirect_uri=<registered_redirect_uri>&\n  scope=accounts:read%20transactions:read%20offline_access&\n  state=<opaque_state>&\n  code_challenge=<pkce_challenge>&\n  code_challenge_method=S256&\n  audience=https%3A%2F%2Frhoapi.rho.co\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["response_type"]}," - always ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["code"]},"."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["client_id"]}," - issued during onboarding."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["redirect_uri"]}," - must exactly match a registered redirect URI."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["scope"]}," - space-separated subset of your registered scopes. Include ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["offline_access"]}," if you need refresh tokens."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["state"]}," - an opaque value your app verifies on the redirect back."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["code_challenge"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["code_challenge_method"]}," - required. PKCE with ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["S256"]},"."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["audience"]}," - the Rho API your app will call: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["https://rhoapi.rho.co"]},"."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"step-2-the-customer-approves-your-app","__idx":4},"children":["Step 2: The customer approves your app"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The customer signs in to Rho, selects the business they want to connect, and grants the requested access on the consent screen. Only Account Owners and Admins can perform this action."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["A business can have at most one active grant per app - approving again updates the existing connection."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"step-3-receive-the-authorization-code","__idx":5},"children":["Step 3: Receive the authorization code"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["After approval, the customer's browser is redirected to your ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["redirect_uri"]}," with an authorization code:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"text","header":{"controls":{"copy":{}}},"source":"https://yourapp.example.com/callback?code=<authorization_code>&state=<opaque_state>\n","lang":"text"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Verify that ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["state"]}," matches the value you sent. Authorization codes are single-use and expire after a few minutes."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["If the customer declines, or has no business where they are allowed to approve integrations, the redirect carries ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["error=access_denied"]}," instead of a code."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"step-4-exchange-the-code-for-tokens","__idx":6},"children":["Step 4: Exchange the code for tokens"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Exchange the authorization code at the token endpoint:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"http","header":{"controls":{"copy":{}}},"source":"POST /oauth2/token HTTP/1.1\nHost: auth.rho.co\nContent-Type: application/x-www-form-urlencoded\nAuthorization: Basic <base64(client_id:client_secret)>\n\ngrant_type=authorization_code&\ncode=<authorization_code>&\nredirect_uri=<registered_redirect_uri>&\ncode_verifier=<pkce_verifier>\n","lang":"http"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The response contains a bearer access token and, if ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["offline_access"]}," was granted, a refresh token:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"json","header":{"controls":{"copy":{}}},"source":"{\n  \"access_token\": \"<access_token>\",\n  \"token_type\": \"bearer\",\n  \"expires_in\": 900,\n  \"refresh_token\": \"<refresh_token>\",\n  \"scope\": \"accounts:read transactions:read offline_access\"\n}\n","lang":"json"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"step-5-call-the-api","__idx":7},"children":["Step 5: Call the API"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Send the access token in the standard HTTP ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Authorization"]}," header using the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Bearer"]}," scheme:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"bash","header":{"controls":{"copy":{}}},"source":"curl https://rhoapi.rho.co/api/v1/transactions \\\n  -H \"Authorization: Bearer $ACCESS_TOKEN\"\n","lang":"bash"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"step-6-refresh-tokens","__idx":8},"children":["Step 6: Refresh tokens"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["When the access token expires, use the refresh token to get a new one:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"http","header":{"controls":{"copy":{}}},"source":"POST /oauth2/token HTTP/1.1\nHost: auth.rho.co\nContent-Type: application/x-www-form-urlencoded\n\ngrant_type=refresh_token&\nrefresh_token=<refresh_token>\n","lang":"http"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Refresh tokens are ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["single-use and rotate on every refresh"]},": each response includes a new refresh token, and the old one stops working. Persist the new token immediately."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Access tokens expire after ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["15 minutes"]},". Refresh tokens last ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["30 days"]}," on a rolling basis - each refresh starts a new 30-day window. The grant itself lasts ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["1 year"]},"; after that the customer must re-approve your app."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["All ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["auth.rho.co"]}," endpoint URLs are also published at ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["https://auth.rho.co/.well-known/openid-configuration"]}," for OAuth libraries that support discovery."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"revoking-access","__idx":9},"children":["Revoking access"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Your app can revoke its own tokens at any time:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"http","header":{"controls":{"copy":{}}},"source":"POST /oauth2/revoke HTTP/1.1\nHost: auth.rho.co\nContent-Type: application/x-www-form-urlencoded\n\ntoken=<access_or_refresh_token>&\nclient_id=<your_client_id>&\nclient_secret=<your_client_secret>\n","lang":"http"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Customers can also disconnect your app from their Rho business settings. Customer revocation is ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["immediate"]}," and invalidates all tokens for the grant, including refresh tokens - your next API request returns ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["401 Unauthorized"]},". Handle this gracefully by sending the customer through the ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"#authorization-flow"},"children":["authorization flow"]}," again."]}]},"headings":[{"value":"Partner Authentication","id":"partner-authentication","depth":1},{"value":"Getting access","id":"getting-access","depth":2},{"value":"Authorization flow","id":"authorization-flow","depth":2},{"value":"Step 1: Redirect the customer to Rho","id":"step-1-redirect-the-customer-to-rho","depth":3},{"value":"Step 2: The customer approves your app","id":"step-2-the-customer-approves-your-app","depth":3},{"value":"Step 3: Receive the authorization code","id":"step-3-receive-the-authorization-code","depth":3},{"value":"Step 4: Exchange the code for tokens","id":"step-4-exchange-the-code-for-tokens","depth":3},{"value":"Step 5: Call the API","id":"step-5-call-the-api","depth":3},{"value":"Step 6: Refresh tokens","id":"step-6-refresh-tokens","depth":3},{"value":"Revoking access","id":"revoking-access","depth":2}],"frontmatter":{"seo":{"title":"Partner Authentication"}},"lastModified":"2026-07-13T09:03:20.000Z","pagePropGetterError":{"message":"","name":""}},"slug":"/docs/v1/partner-auth","userData":{"isAuthenticated":false,"teams":["anonymous"]},"isPublic":true}