# Partner Authentication

Partner Authentication lets your application access the Rho API on behalf of Rho customers using **OAuth 2.0**.

Rho uses the standard **Authorization Code flow with PKCE**. The authorization server is `https://auth.rho.co`.

If you are building an internal integration for your own account, you don't need OAuth - use an [API Access Token](/docs/v1/auth) instead.

## Getting access

OAuth clients are registered by Rho. To onboard as a partner, email the following details to `api-partner-request@rho.co`:

| Field | Notes |
|  --- | --- |
| **Client / app name** | Shown to customers on the consent screen. |
| **Company name** | Your legal entity name. |
| **Logo** | Your app's logo image. Shown on the consent screen. |
| **Redirect URI(s)** | All URIs you need, production, development, local. |
| **Requested scopes** | The scopes your app needs, e.g. `accounts:read transactions:read offline_access` (see [Scopes](/docs/v1/auth#scopes)). |
| **Privacy policy URI** | Linked from the consent screen. |
| **Terms of Service URI** | Linked from the consent screen. |
| **Support / contact email** | Used for operational and security notices about your integration. |


After review, Rho registers your OAuth client and sends you your `client_id` and `client_secret`. Treat the secret like a password: store it in a secret manager and never share it with anyone.

## Authorization flow

### Step 1: Redirect the customer to Rho

Send the customer's browser to the Rho authorization URL:

```bash
GET https://auth.rho.co/oauth2/auth?
  response_type=code&
  client_id=<your_client_id>&
  redirect_uri=<registered_redirect_uri>&
  scope=accounts:read%20transactions:read%20offline_access&
  state=<opaque_state>&
  code_challenge=<pkce_challenge>&
  code_challenge_method=S256&
  audience=https%3A%2F%2Frhoapi.rho.co
```

- `response_type` - always `code`.
- `client_id` - issued during onboarding.
- `redirect_uri` - must exactly match a registered redirect URI.
- `scope` - space-separated subset of your registered scopes. Include `offline_access` if you need refresh tokens.
- `state` - an opaque value your app verifies on the redirect back.
- `code_challenge`, `code_challenge_method` - required. PKCE with `S256`.
- `audience` - the Rho API your app will call: `https://rhoapi.rho.co`.


### Step 2: The customer approves your app

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.

A business can have at most one active grant per app - approving again updates the existing connection.

### Step 3: Receive the authorization code

After approval, the customer's browser is redirected to your `redirect_uri` with an authorization code:

```text
https://yourapp.example.com/callback?code=<authorization_code>&state=<opaque_state>
```

Verify that `state` matches the value you sent. Authorization codes are single-use and expire after a few minutes.

If the customer declines, or has no business where they are allowed to approve integrations, the redirect carries `error=access_denied` instead of a code.

### Step 4: Exchange the code for tokens

Exchange the authorization code at the token endpoint:

```http
POST /oauth2/token HTTP/1.1
Host: auth.rho.co
Content-Type: application/x-www-form-urlencoded
Authorization: Basic <base64(client_id:client_secret)>

grant_type=authorization_code&
code=<authorization_code>&
redirect_uri=<registered_redirect_uri>&
code_verifier=<pkce_verifier>
```

The response contains a bearer access token and, if `offline_access` was granted, a refresh token:

```json
{
  "access_token": "<access_token>",
  "token_type": "bearer",
  "expires_in": 900,
  "refresh_token": "<refresh_token>",
  "scope": "accounts:read transactions:read offline_access"
}
```

### Step 5: Call the API

Send the access token in the standard HTTP `Authorization` header using the `Bearer` scheme:

```bash
curl https://rhoapi.rho.co/api/v1/transactions \
  -H "Authorization: Bearer $ACCESS_TOKEN"
```

### Step 6: Refresh tokens

When the access token expires, use the refresh token to get a new one:

```http
POST /oauth2/token HTTP/1.1
Host: auth.rho.co
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token&
refresh_token=<refresh_token>
```

Refresh tokens are **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.

Access tokens expire after **15 minutes**. Refresh tokens last **30 days** on a rolling basis - each refresh starts a new 30-day window. The grant itself lasts **1 year**; after that the customer must re-approve your app.

All `auth.rho.co` endpoint URLs are also published at `https://auth.rho.co/.well-known/openid-configuration` for OAuth libraries that support discovery.

## Revoking access

Your app can revoke its own tokens at any time:

```http
POST /oauth2/revoke HTTP/1.1
Host: auth.rho.co
Content-Type: application/x-www-form-urlencoded

token=<access_or_refresh_token>&
client_id=<your_client_id>&
client_secret=<your_client_secret>
```

Customers can also disconnect your app from their Rho business settings. Customer revocation is **immediate** and invalidates all tokens for the grant, including refresh tokens - your next API request returns `401 Unauthorized`. Handle this gracefully by sending the customer through the [authorization flow](#authorization-flow) again.