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

# Runbook: Session Key setup

# Runbook: Session Key setup

Use this runbook to create the key that sesame uses to read your Polymarket account and to
trade. The key is a Polymarket **Session Key**: it can place and cancel orders for your
Deposit Wallet, but it is not the account's owner key. The owner key never goes into this app.
For combos, the key also needs the combos scope ([section 8](#8-combos-scope)).

The words this page uses are in the deploy guide's
[Words used in these pages](../guides/deploy/index.md#words-used-in-these-pages). Each setting
is in the [configuration reference](../guides/configuration.md#to-connect-your-polymarket-account).

Background: [Polymarket venue notes §10](../venue/polymarket.md#10-session-keys),
[secrets and logging policy](../security/secrets-and-logging.md),
[ADR 0009](../adr/0009-venue-credentials-in-memory.md) and
[ADR 0012](../adr/0012-session-key-scope.md). Official Polymarket documentation:

* Session Keys: [https://docs.polymarket.com/trading/session-keys](https://docs.polymarket.com/trading/session-keys)
* Wallets and authentication: [https://docs.polymarket.com/trading/wallets-auth](https://docs.polymarket.com/trading/wallets-auth)
* API authentication: [https://docs.polymarket.com/getting-started/api](https://docs.polymarket.com/getting-started/api)

## 1. Before you start

You need these things:

| Item | Detail |
| - | - |
| A Deposit Wallet | Session Keys work only with a Deposit Wallet. Each Polymarket account wallet made on or after 4 May 2026 is one. Older Proxy and Safe wallets cannot use Session Keys |
| The Deposit Wallet address | Copy it from the profile menu on polymarket.com. It is not the address you sign in with, and not the Session Key's address |
| A Builder API key | You need it to authorise a Session Key. Create it on polymarket.com, under **Settings**, then **Builders**. During the beta, Polymarket must also approve it for Session Key management: send an email to [builder@polymarket.com](mailto:builder@polymarket.com). The Builder API key stays on the owner machine. It never goes into this app |
| Two machines | The **owner machine** holds the owner key and the Builder API key. The **app host** runs sesame. On the server, the app host is the server |
| A permitted location | Create the key, and run the app with it, only where Polymarket permits you to trade. The app does not work around the location check ([ADR 0005](../adr/0005-no-geoblock-workarounds.md)) |

Session Keys are in beta. Polymarket can change the steps. Read the official page before each
rotation.

CAUTION: Do not put the Session Key on the development server. That server is in a blocked
location (security finding F8). Development there uses the fake venue (`task dev:fake`).

What a Session Key can and cannot do:

| Subject | What it does |
| - | - |
| Scope | `CLOB`: place and cancel orders, and read its own orders and trades. `COMBOSRFQ`: request and accept combo quotes. `ALL`: both, and each venue Polymarket adds later. Polymarket enforces the scope off-chain. The wallet contract keeps only the expiry ([ADR 0012](../adr/0012-session-key-scope.md)) |
| Funds | Polymarket's documentation says that a Session Key cannot withdraw funds. The wallet contract does not enforce this. Only Polymarket's relayer stops a leaked key from a transfer out of the Deposit Wallet ([ADR 0012](../adr/0012-session-key-scope.md)). Keep the Deposit Wallet within the working balance in [ADR 0013](../adr/0013-working-balance-cap.md). The app never moves funds |
| Expiry | Always 180 days after you authorise it. A shorter time is not possible. To end it early, revoke it |
| Visibility | It sees only the orders and trades that it placed. It does not see orders placed on polymarket.com |
| Revocation | Stops its trading and cancels its open orders |

## 2. Create the key

### 2.1 Make the key pair on the app host

1. On the app host, make a new key pair. Any standard tool can do this. With Foundry, type:

   ```bash theme={null}
   cast wallet new
   ```

   The command shows an address and a private key.

2. Keep the private key on the app host. You need it in [section 3](#3-configure-the-app).

3. Copy only the address to the owner machine.

WARNING: Do not paste the private key into a chat, an email, a pull request, a ticket or an
AI agent session. If you must make the key pair on another machine, move the private key over
an encrypted connection. Then delete the copy.

### 2.2 Authorise the key from the owner machine

1. On the owner machine, use the official TypeScript SDK (`@polymarket/client`) or the Python
   SDK (`polymarket`). With the TypeScript SDK, run this code:

   ```ts theme={null}
   import { createSecureClient, SessionKeyKnownScope } from "@polymarket/client";
   import { builderApiKey } from "@polymarket/client/node";
   import { privateKey } from "@polymarket/client/viem";

   const secureClient = await createSecureClient({
     signer: privateKey(process.env.OWNER_PRIVATE_KEY),   // the owner key, on this machine only
     wallet: process.env.DEPOSIT_WALLET,                   // the Deposit Wallet address
     apiKey: builderApiKey({
       key: process.env.BUILDER_API_KEY,
       secret: process.env.BUILDER_SECRET,
       passphrase: process.env.BUILDER_PASSPHRASE,
     }),
   });

   await secureClient.authorizeSessionKey({
     address: "<session key address from 2.1>",
     scopes: [SessionKeyKnownScope.CLOB],
   });
   ```

2. Write down the expiry date: 180 days from today. You must rotate the key before that date
   ([section 5](#5-rotation)).

* Always give `scopes`. If you do not, the SDK uses `ALL`.
* Without combos, give `CLOB` only. For combos, give `CLOB` and `COMBOSRFQ`, not `ALL`
  ([section 8](#8-combos-scope)). `ALL` also covers each venue Polymarket adds later (security
  finding P8-11).
* The key works when it is in the wallet's list of session signers. The SDK waits for this.
* The environment variable names in the code are for the owner machine only. Never put the
  owner key or the Builder API key in this app's settings.

## 3. Configure the app

1. On the server, open the settings file:

   ```sh theme={null}
   sudoedit /opt/sesame/sesame.env
   ```

2. Add these lines, with your values:

   ```dotenv theme={null}
   POLYMARKET_WALLET_ADDRESS=<Deposit Wallet address>
   POLYMARKET_SESSION_KEY=<Session Key private key>
   POLYMARKET_OWNER_ADDRESS=<owner address>
   ```

3. Save the file and close the editor.

4. On your computer, restart the app:

   ```sh theme={null}
   task deploy:start
   ```

   The command ends with a table that lists the `sesame` and `caddy` services.

5. Do the checks in [section 4](#4-check-that-it-worked).

In development, put the same lines in the `.env` file in the repository root. Make the file
readable only by the account that runs the app: mode `0600` on Linux, or `icacls` on Windows.
The deploy guide gives the full go-live steps in
[Go live, step 3](../guides/deploy/4-go-live.md#step-3-add-the-account-settings).

| Setting | Required | Value |
| - | - | - |
| `POLYMARKET_WALLET_ADDRESS` | Yes, for any account data | The Deposit Wallet address. Not the owner's address and not the Session Key's address |
| `POLYMARKET_SESSION_KEY` | Yes, for cash, orders and trading | The private key from step 2.1, in hex |
| `POLYMARKET_OWNER_ADDRESS` | No, but recommended | The owner's public address. With it, the app does not start if the owner key is in `POLYMARKET_SESSION_KEY` |
| `POLYMARKET_CLOB_API_KEY`, `POLYMARKET_CLOB_SECRET`, `POLYMARKET_CLOB_PASSPHRASE` | No | Leave all three empty |

* **Leave the three `POLYMARKET_CLOB_*` settings empty.** At each start, the app makes the
  CLOB API credentials from the Session Key. It keeps them in memory only, never on disk, in
  the database or in the log ([ADR 0009](../adr/0009-venue-credentials-in-memory.md)). If you
  set them, the app uses them and makes none. Set all three or none.
* **The old name is refused.** If the file has a `POLYMARKET_PRIVATE_KEY` line, even an empty
  one, the app does not start. Rename it to `POLYMARKET_SESSION_KEY`. Make sure that the value
  is a Session Key, not the owner key.
* **Other refused lines.** The app also does not start with `GEOBLOCK_URL`, or with a name that
  starts with `POLYMARKET_RELAYER_`, `POLYMARKET_BRIDGE_URL`, `POLY_BUILDER_` or
  `POLYMARKET_BUILDER_`. The Builder API key stays on the owner machine.

What the app shows with each set of settings:

| Settings | Positions, activity, P\&L | Cash, open orders, fills, trading |
| - | - | - |
| None | No | No |
| Wallet address only | Yes. The app reads them by address | No |
| Wallet address and Session Key | Yes | Yes, when the credentials are ready |

## 4. Check that it worked

1. **The app starts.** If a setting is wrong, the app does not start and its log names the
   setting. The log never shows the key. The app stops when:

   * the Session Key is not 64 hex characters, with or without `0x`, or is not a valid
     private key
   * an address is not 40 hex characters, with or without `0x`
   * the Session Key's address, `POLYMARKET_SIGNER_ADDRESS` or the wallet address is the same
     as `POLYMARKET_OWNER_ADDRESS`
   * the wallet address is the same as the Session Key's address
   * a Session Key or CLOB credentials are set without `POLYMARKET_WALLET_ADDRESS`
   * only some of the three `POLYMARKET_CLOB_*` settings are set, or a refused line is in the
     file

   What to do for each message is in
   [deploy troubleshooting](../guides/deploy/troubleshooting.md#the-deploy-stops-and-the-app-does-not-start).

2. **The start log confirms the key.** On your computer, read the log:

   ```sh theme={null}
   task deploy:logs
   ```

   Find the line `session key confirmed`. Its `signer` field is the Session Key's address. Its
   `validUntil` field is the expiry. Its `scopes` field lists the scopes, for example `CLOB`.

3. **Settings shows the account.** In the app, open **Settings > Account**. Make sure that:

   * **Credentials** shows **Connected**
   * **Signer address (Session Key)** is the address from step 2.1, not the owner's address
   * **Wallet address (Deposit Wallet)** is the Deposit Wallet address

   The **Credentials** row shows one of these:

   | Label | Meaning |
   | - | - |
   | **Connected** | The credentials are ready and the last call with them worked |
   | **Connecting** | The app makes the credentials now. The tooltip says `Account credentials pending` |
   | **Account error** | The credentials failed. The tooltip says `Account credentials failed: <reason>`. See [section 7](#7-troubleshooting) |
   | **Read-only** | The wallet address is set, but no Session Key. Positions show, cash and orders do not |
   | **Not connected** | No wallet address is set |

   The top bar shows the same label beside **Cash** when the state is not **Connected**.

4. **The figures match.** Compare the top bar with polymarket.com:
   * Positions and cash must be the same.
   * The app shows only the orders that this Session Key placed. Orders placed on
     polymarket.com do not show in the app.

## 5. Rotation

Rotate the key before its 180 days end. Rotate it at once if the key may be exposed: in a log,
a commit, a screenshot, a chat or an agent session, or on a host that may be compromised.

1. Make a new key pair on the app host ([2.1](#21-make-the-key-pair-on-the-app-host)).
2. Authorise it from the owner machine ([2.2](#22-authorise-the-key-from-the-owner-machine)).
3. Put the new key in `POLYMARKET_SESSION_KEY` ([section 3](#3-configure-the-app)).
4. If you set the `POLYMARKET_CLOB_*` settings, clear them. They belong to the old key.
5. Restart the app.
6. Make sure that **Settings > Account** shows the **new** signer address and **Connected**
   ([section 4](#4-check-that-it-worked)).
7. Revoke the old key from the owner machine ([section 6](#6-revocation)).

CAUTION: Revocation cancels the open orders of the old key. Do step 7 only when that is
acceptable.

The CLOB credentials need no rotation of their own. They belong to the Session Key and the app
makes them again at each start.

How the app shows the expiry:

| When | What you see |
| - | - |
| 14 days or less before the expiry | The top bar shows `Key expires in <n>d`. **Settings > Account** shows a **Session Key expiry** row |
| 24 hours before the expiry | A `Session Key expiring` notification, in the app only by default |
| After the expiry | Orders are refused. **Armed** goes back to **Safe**. The top bar shows `Session Key expired`. The switch shows `Trading off · Session Key expired` |
| At a start after the expiry | The credentials fail with `wallet_mismatch` |

## 6. Revocation

1. On the owner machine, use the same SDK client as in step 2.2:

   ```ts theme={null}
   await secureClient.revokeSessionKey({ address: "<session key address>" });
   ```

2. Wait for the call to return. The key is then not active.

* Revocation stops the key from trading and cancels its open orders. It does not change the
  orders of other Session Keys or of the owner.
* Polymarket finishes the cancels and the on-chain step after the call returns.
* After revocation, the app's next call with the credentials gets status 401. The app then
  checks its clock and tries to make new credentials. When that fails, the credentials show
  **Account error** with `auth_rejected`. If the clock is more than 2 seconds wrong, the
  reason is `clock_skew`.

If the Session Key or the host may be compromised, do these steps at once:

1. In the app, click **Cancel all**.
2. Set the switch to **Safe**.
3. Set `LIVE_TRADING=false` in the settings file and restart the app.
4. Revoke the Session Key from the owner machine (step 1 above). It is not enough to revoke
   the CLOB credentials, because the key can make new ones. The key may be able to move funds
   ([ADR 0012](../adr/0012-session-key-scope.md)). If the relayer is not available, use the
   owner-only emergency path in ADR 0012: pause, wait one hour, then revoke.
5. On polymarket.com, look for trades in your positions and history that the app did not
   make.
6. Follow the compromise steps in the
   [secrets and logging policy](../security/secrets-and-logging.md#5-rotation).

## 7. Troubleshooting

When the credentials fail, **Settings > Account** shows `Account credentials failed: <reason>`.
The reason is a fixed code. Polymarket's own error text goes only to the app's log.

| Reason | Meaning | What to do | Does the app try again? |
| - | - | - | - |
| `derive_failed` | The app could not make the CLOB API credentials from the Session Key | Make sure that the key is a valid private key, that it is authorised for this Deposit Wallet (2.2) and that it is not expired. Read the log for Polymarket's reply | Only after a network error |
| `auth_rejected` | Polymarket refused the credentials | The key is revoked or expired, or the `POLYMARKET_CLOB_*` values belong to another key. Clear those values, or make a new key ([section 5](#5-rotation)) | Yes, after 5 minutes, 15 minutes, 1 hour, then each hour |
| `clock_skew` | The server's clock differs from Polymarket's by more than 2 seconds | Correct the server's clock | Yes, as for `auth_rejected`. The app also checks the clock each 30 seconds |
| `geoblocked` | Polymarket refused the request because of the server's location | Nothing in the app changes this. Run the app only where you are permitted to trade ([ADR 0005](../adr/0005-no-geoblock-workarounds.md)) | No |
| `network` | The app could not reach Polymarket | Make sure that the server can connect to Polymarket | Yes, after 30 seconds, then after double the last wait, up to 10 minutes |
| `wallet_mismatch` | Polymarket does not list this Session Key for `POLYMARKET_WALLET_ADDRESS` with the `CLOB` or `ALL` scope, or the key is expired | See below | No. Restart the app after the fix |

For `wallet_mismatch`:

1. Make sure that `POLYMARKET_WALLET_ADDRESS` is the Deposit Wallet that you authorised the key
   for in step 2.2.
2. Make sure that the key has the `CLOB` or `ALL` scope and that you did not revoke it. A key
   with `COMBOSRFQ` alone gives this reason, because the app needs `CLOB` too.
3. Make sure that the key is not expired. If it is, make a new key ([section 5](#5-rotation)).
4. Read the log for `session signers listed for another wallet`. If you find it, Polymarket
   answered for another wallet. Its `wallet` and `venueWallet` fields show the two addresses,
   with only the first 6 and last 4 characters. Either `POLYMARKET_WALLET_ADDRESS` holds
   another address, such as the owner's, or you authorised the key for another account.

Other problems:

| What you see | What to do |
| - | - |
| The app does not start and the log names `POLYMARKET_PRIVATE_KEY` | Rename the line to `POLYMARKET_SESSION_KEY` ([section 3](#3-configure-the-app)) |
| The app does not start because the Session Key's address equals the owner address | The key in the file is the owner key. Remove it from the host now. Make a Session Key ([section 2](#2-create-the-key)). If other people use the host, treat the owner key as exposed |
| **Read-only** in the top bar. Positions show, but not cash or orders | The wallet address is set, but no Session Key. Add the key ([section 3](#3-configure-the-app)) |
| The orders page shows `Venue credentials failed` or `No venue credentials` | The credentials are not ready. Read the **Credentials** row in **Settings > Account** |
| Orders placed on polymarket.com do not show | This is correct. A Session Key sees only its own orders |
| Positions or cash look wrong | Make sure that `POLYMARKET_WALLET_ADDRESS` is the Deposit Wallet, not the owner's or the Session Key's address |
| `Combos unavailable · Session Key lacks the combo scope` | The key does not have the combos scope. See [section 8](#8-combos-scope) |

## 8. Combos scope

Combos get quotes from Polymarket's market makers. To accept a quote, the app signs an order.
The app sends these requests with the Session Key's credentials. Polymarket accepts them only
from a key with the `COMBOSRFQ` or `ALL` scope
([ADR 0014](../adr/0014-combos-requester-api.md)). A key with `CLOB` alone can still trade
other markets, but combos stay off.

### 8.1 How a `CLOB`-only key shows

| Where | What it shows |
| - | - |
| The `Combo` toggle and **Get quote** | Not available. The tooltip is `Combos unavailable · Session Key lacks the combo scope` |
| The start log | `session key confirmed` with `scopes` that do not include `COMBOSRFQ` or `ALL` |
| `GET /api/account` | `credentials.scopes` is `["trading"]` |
| `GET /api/trading` | `combos_can_trade` is `false`, and `combos_reasons` lists `combos_unavailable` |
| A quote request sent anyway | The app refuses it with `combos_unavailable`. Nothing goes to Polymarket |
| Combo positions and combo activity | These show as usual. They need only the wallet address |

The app reads the scopes from Polymarket when it starts. After you change the key, restart the
app.

### 8.2 Issue a key with the combos scope

This makes a new key, then revokes the old one, as in a rotation ([section 5](#5-rotation)).

1. In the app, click **Cancel all**.

2. Set the switch to **Safe**.

3. On polymarket.com, make sure that no order from the app is still open. The new key cannot
   see the orders of the old key, and revocation cancels them.

4. Make a new key pair on the app host ([2.1](#21-make-the-key-pair-on-the-app-host)).

5. On the owner machine, authorise it with the same client as in step 2.2, with both scopes:

   ```ts theme={null}
   await secureClient.authorizeSessionKey({
     address: "<new session key address>",
     scopes: [SessionKeyKnownScope.CLOB, SessionKeyKnownScope.COMBOSRFQ],
   });
   ```

6. Put the new key in `POLYMARKET_SESSION_KEY`. Clear the `POLYMARKET_CLOB_*` settings if you
   set them ([section 3](#3-configure-the-app)).

7. Restart the app.

8. Make sure that:
   * **Settings > Account** shows the **new** signer address and **Connected**
   * the `session key confirmed` log line lists `CLOB` and `COMBOSRFQ` in `scopes`
   * `GET /api/account` shows `credentials.scopes` as `["trading","combos"]`
   * `GET /api/trading` does not list `combos_unavailable` in `combos_reasons`. While the app
     is **Safe**, it lists `not_armed`. You can get a quote while the app is **Safe**

9. Revoke the old key from the owner machine ([section 6](#6-revocation)). Do this only after
   the new key shows **Connected**, so that the app always has a key that works.

About the scopes in step 5:

* Use `CLOB` and `COMBOSRFQ`, not `ALL` (security finding P8-11). `ALL` also works, but it
  covers each venue Polymarket adds later.
* Do not use `COMBOSRFQ` alone. The app needs `CLOB` too and shows such a key as
  `wallet_mismatch` ([section 7](#7-troubleshooting)).
* The choice does not change what a leaked key can do on-chain
  ([ADR 0012](../adr/0012-session-key-scope.md)). The working-balance limit in
  [ADR 0013](../adr/0013-working-balance-cap.md) stays the same.
* The new key is valid for 180 days.

### 8.3 Exchange approvals

A combo buy spends pUSD through Polymarket's Combos exchange (Exchange V3). A combo sell gives
combo shares to that exchange. Both need the wallet's approval of the exchange: pUSD for buys,
combo positions for sells.

1. Set both approvals on polymarket.com, as the owner.

The app cannot set or read these approvals, and has no code for them. If an approval is
not set, Polymarket refuses **Accept**. The app shows `Rejected · not enough cash or
allowance` (`venue_balance`).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.