# Authentication

> API keys for servers and agents, session tokens for browsers.

Source: https://www.senddy.com/docs/developers/authentication
Last modified: 2026-10-07T18:10:55.000Z

---

Every call to the Senddy API is authenticated. There are two kinds of credential, and which one you use depends on where your code runs.

| Where your code runs | Use | Why |
|---|---|---|
| Your server, a script, an agent | API key | Simple, long-lived, kept secret on your side |
| A user's browser or phone | Session token | Short-lived, per user, safe to ship to the client |

## API keys

Create keys in the [Developer Dashboard](/developer). Pass one to the client as `apiKey`, and the client sends it as `X-API-Key` on every request.

```typescript
const senddy = createSenddyClient({
  // …chain config
  apiKey: process.env.SENDDY_API_KEY,
});
```

Never put an API key in browser code. Anyone can read it from your bundle, and every user would share one rate limit.

## Session tokens

In a browser, your server mints a short-lived token for each signed-in user, and the client uses that instead of a key.

**On your server**, after you've verified the user is signed in:

```typescript
const res = await fetch(`${SENDDY_API_URL}/session`, {
  method: 'POST',
  headers: { 'content-type': 'application/json', 'x-api-key': process.env.SENDDY_API_KEY! },
  body: JSON.stringify({ sub: user.id, perms: ['read', 'write'], ttl_secs: 900 }),
});
const { token, exp } = await res.json(); // token starts with "sst_"
```

Require an explicit `Authorization` header on that route, not just a cookie, so another site can't mint tokens for a signed-in user. Throttle it per user.

**In the browser**, give the client a provider function. It's called when a token is needed, and again if one expires, after which the request is retried once.

```typescript
const senddy = createSenddyClient({
  // …chain config
  sessionToken: async () => {
    const res = await fetch('/api/session-token', { method: 'POST', headers: { authorization: `Bearer ${userAuthToken}` } });
    return (await res.json()).token;
  },
});
```

Cache the token in the provider and refresh it shortly before it expires. When `sessionToken` is set, the client doesn't send `apiKey`.

### Using Privy

If your app signs users in with Privy, you can skip the server route. Register your Privy app with us at [developers@senddy.com](mailto:developers@senddy.com), and the browser can exchange the user's Privy access token for a session directly at `POST /session/exchange`.

## Rate limits

| | Limit |
|---|---|
| Per signed-in user, with a session token | 120 requests a minute |
| Per developer | 60 requests a minute and 10,000 a day by default. [Ask us](mailto:developers@senddy.com) for more |

Requests over a limit get a `429`. A request with an expired session token gets `401 SESSION_EXPIRED`, which the client handles for you.
