> ## Documentation Index
> Fetch the complete documentation index at: https://partner.tren.ch/llms.txt
> Use this file to discover all available pages before exploring further.

# Connect a User

> Open the Trench consent popup with PKCE, relay the authorization code from your callback, and exchange it for a token pair on your backend.

This guide walks you through connecting a Trench account to your application, ending with an access token you can trade with.

There are three moving parts: a popup that you open, a callback page that you host, and a token exchange that runs on your backend.

## Prerequisites

Before starting, make sure you have:

* An app registered at [tren.ch/partner](https://tren.ch/partner), giving you a `client_id` and an API key. See [Registration](/concepts/registration).
* At least one redirect URI registered, matching exactly what you will send.
* A backend that can hold the client secret, since it must never reach the browser.

## Endpoints Used

* `GET https://tren.ch/oauth/authorize` for the consent screen
* [`POST /oauth/token`](/api-reference/oauth-token) to exchange the code

## 1. Open the Consent Popup

Generate a PKCE verifier and a `state` value, then open the popup. The window needs to be opened synchronously inside the click handler, otherwise the browser will block it.

```javascript theme={null}
const TRENCH = "https://tren.ch";

function base64url(bytes) {
  return btoa(String.fromCharCode(...bytes))
    .replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
}

document.querySelector("#connect-trench").addEventListener("click", async () => {
  // The verifier stays local. Only its hash goes in the URL.
  const verifier = base64url(crypto.getRandomValues(new Uint8Array(48)));
  const state = base64url(crypto.getRandomValues(new Uint8Array(16)));
  const challenge = base64url(new Uint8Array(
    await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier))
  ));

  sessionStorage.setItem("trench_verifier", verifier);
  sessionStorage.setItem("trench_state", state);

  // Open the window first, then point it at the URL.
  const popup = window.open("about:blank", "trench", "width=460,height=720");
  popup.location = `${TRENCH}/oauth/authorize?` + new URLSearchParams({
    client_id: YOUR_CLIENT_ID,
    redirect_uri: "https://yourapp.com/oauth/callback",
    scope: "profile:read trade:execute governance:vote",
    state,
    code_challenge: challenge,
    code_challenge_method: "S256",
  });

  window.addEventListener("message", (event) => {
    if (event.origin !== window.location.origin) return;
    if (event.data?.type !== "trench:oauth") return;
    if (event.data.state !== sessionStorage.getItem("trench_state")) return;
    exchangeOnYourBackend(event.data.code, sessionStorage.getItem("trench_verifier"));
  }, { once: true });
});
```

All six parameters are required.

| Parameter               | Constraint                                   |
| ----------------------- | -------------------------------------------- |
| `client_id`             | Your registered client                       |
| `redirect_uri`          | Exact match against a registered URI         |
| `scope`                 | Space-separated list                         |
| `state`                 | Random per authorization, verified on return |
| `code_challenge`        | 43 to 128 characters of `[A-Za-z0-9_-]`      |
| `code_challenge_method` | Must be `S256`                               |

The popup runs on the Trench origin, so the user sees `tren.ch` in the address bar and can confirm who they are approving. Your page cannot read anything inside it.

## 2. Relay the Code From Your Callback

The popup lands on your `redirect_uri` with `?code=…&state=…` in the query string. At that point it is same-origin with the opener, so it can post the code back and close itself.

```html theme={null}
<!-- https://yourapp.com/oauth/callback -->
<script>
  const q = new URLSearchParams(location.search);

  window.opener?.postMessage({
    type: "trench:oauth",
    code: q.get("code"),
    state: q.get("state"),
    error: q.get("error"),
  }, location.origin);

  window.close();
</script>
```

If the user declines, you will receive `?error=access_denied&state=…` and no code.

<Warning>
  Always compare the returned `state` against the value you stored before the popup opened. Skipping this check leaves the flow open to CSRF.
</Warning>

## 3. Exchange the Code

Send the code and the verifier from your backend, authenticating with HTTP Basic.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST 'https://api.tren.ch/oauth/token' \
    -u "$CLIENT_ID:$CLIENT_SECRET" \
    -d grant_type=authorization_code \
    -d code=trench_ac_YOUR_CODE \
    -d redirect_uri=https://yourapp.com/oauth/callback \
    -d code_verifier=YOUR_VERIFIER
  ```

  ```javascript Node.js theme={null}
  const basic = Buffer.from(`${CLIENT_ID}:${CLIENT_SECRET}`).toString("base64");

  const res = await fetch("https://api.tren.ch/oauth/token", {
    method: "POST",
    headers: {
      Authorization: `Basic ${basic}`,
      "Content-Type": "application/x-www-form-urlencoded",
    },
    body: new URLSearchParams({
      grant_type: "authorization_code",
      code,
      redirect_uri: "https://yourapp.com/oauth/callback",
      code_verifier: verifier,
    }),
  });

  const tokens = await res.json();
  ```

  ```python Python theme={null}
  import requests

  res = requests.post(
    "https://api.tren.ch/oauth/token",
    auth=(CLIENT_ID, CLIENT_SECRET),
    data={
      "grant_type": "authorization_code",
      "code": code,
      "redirect_uri": "https://yourapp.com/oauth/callback",
      "code_verifier": verifier,
    },
  )

  tokens = res.json()
  ```
</CodeGroup>

```json Response theme={null}
{
  "access_token": "trench_at_…",
  "token_type": "Bearer",
  "expires_in": 1800,
  "refresh_token": "trench_rt_…",
  "scope": "profile:read trade:execute governance:vote"
}
```

Store both tokens against your user record. Authorization codes are single use and expire after 10 minutes, and presenting one a second time will revoke every token it issued.

## 4. Confirm the Connection

Call [`GET /partner/v1/me`](/api-reference/me) with the new access token.

```bash cURL theme={null}
curl 'https://api.tren.ch/partner/v1/me' \
  -H 'Authorization: Bearer trench_at_…'
```

```json theme={null}
{
  "user": {
    "walletSolana": "…",
    "twitterHandle": "…",
    "tradingEnabled": true
  },
  "client": "tclient_…",
  "scopes": ["profile:read", "trade:execute", "governance:vote"]
}
```

The `scopes` array in this response reflects what the token can actually do, which may differ from what you requested at authorize time. We recommend treating it as the source of truth.

## Mobile Fallback

Popups are unreliable on mobile browsers. You can run the same flow by navigating the whole page instead of opening a window, then reading `code` from the query string server-side on your callback route. Nothing else about the flow changes.

## 1-Click Trading

The `trade:execute` and governance write scopes require the user to have delegated their wallet to Trench. The consent screen enforces this, so if delegation is off, the Approve button stays disabled until the user enables it.

This means you will never be issued a trading token that cannot trade at the time it is created. A user can revoke delegation later, though, so we recommend checking `tradingEnabled` before showing trade controls. Once delegation is off, writes fail with `403 delegation_missing`.

## Next Steps

<Columns cols={2}>
  <Card title="Trade Tokens" icon="arrow-right-left" href="/guides/trading">
    Execute your first buy and confirm the fill.
  </Card>

  <Card title="Tokens & Refresh" icon="key" href="/concepts/authentication">
    Keep the connection alive without breaking it.
  </Card>
</Columns>
