> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.withpersona.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.withpersona.com/_mcp/server.

# Server SDK Reference

> Full API reference for the Persona Relay server-side SDK.

This page documents the full server-side API for the Persona Relay SDK.

## Installation

#### Node.js

Available on npm as [`@persona/sdk-node`](https://www.npmjs.com/package/@persona/sdk-node).

**`npm`**

```bash title="npm"
npm install @persona/sdk-node
```

**`yarn`**

```bash title="yarn"
yarn add @persona/sdk-node
```

#### Go

Available as a Go module at [`github.com/persona-id/relay-sdk-go`](https://pkg.go.dev/github.com/persona-id/relay-sdk-go). Public release versions available on [GitHub](https://github.com/persona-id/relay-sdk-go/tags).

```bash
go get github.com/persona-id/relay-sdk-go
```

## Constructor

#### Node.js

```typescript
import Persona from '@persona/sdk-node';

const persona = new Persona({ apiKey: '<your_api_key>' });
```

**Arguments** — `PersonaOptions`

| Option   | Type     | Required | Description                                                                                                                              |
| -------- | -------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `apiKey` | `string` | false    | Your Persona API key. Not required for relay session creation (`persona.relays.create`), but may be required for other Persona API calls |

The environment is inferred from your API key: a sandbox key runs against [sandbox](/relay-sandbox), a production key against production. Requires `@persona/sdk-node` `0.4.0` or later.

#### Go

```go
import persona "github.com/persona-id/relay-sdk-go"

client := persona.New(persona.Options{APIKey: "<your_api_key>"})
```

**Arguments** — `persona.Options`

| Field        | Type           | Required | Description                                                                                                                             |
| ------------ | -------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `APIKey`     | `string`       | false    | Your Persona API key. Not required for relay session creation (`client.Relays.Create`), but may be required for other Persona API calls |
| `HTTPClient` | `*http.Client` | false    | Custom HTTP client. Defaults to a client with a 10-second timeout                                                                       |

The environment is inferred from your API key: a sandbox key runs against [sandbox](/relay-sandbox), a production key against production. Requires `relay-sdk-go` `0.1.1` or later.

## Methods

#### Node.js

Relay methods are available on the `persona.relays` namespace.

#### Go

Relay methods are available on the `client.Relays` namespace.

### `create`

Creates a Persona relay session. No API key required.

#### Node.js

```typescript
const session = await persona.relays.create({
  claimType: "live_human_presence",
  encryptionKeyPem: "<your_public_key_pem>",
});
```

**Arguments** — `CreateRelayParams`

| Field              | Type             | Required | Description                                                                                                                      |
| ------------------ | ---------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `claimType`        | `string`         | true     | The claim type to evaluate. See [Claim types](/relay-schemas#claim-types) for the full list                                      |
| `encryptionKeyPem` | `string \| null` | true     | PEM-encoded RSA public key. If provided, Persona encrypts the claim payload on redemption. Pass `null` to receive plaintext JSON |

**Returns** — `CreateRelayResponse`

| Field                     | Type     | Description                                                                                                            |
| ------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------- |
| `relayToken`              | `string` | Identifies the relay session. Pass to the subsequent `generateClaim` call                                              |
| `relaySecret`             | `string` | Authenticates your server's calls on this session. Keep server-side — never expose to the client                       |
| `relaySessionAccessToken` | `string` | Short-lived token that your server returns to the client for the Embedded Widget, Hosted Flow, iOS SDK, or Android SDK |

#### Go

```go
keyPEM := "<your_public_key_pem>"
session, err := client.Relays.Create(ctx, persona.CreateRelayParams{
	ClaimType:        "live_human_presence",
	EncryptionKeyPEM: &keyPEM,
})
```

**Arguments** — `persona.CreateRelayParams`

| Field              | Type      | Required | Description                                                                                                                     |
| ------------------ | --------- | -------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `ClaimType`        | `string`  | true     | The claim type to evaluate. See [Claim types](/relay-schemas#claim-types) for the full list                                     |
| `EncryptionKeyPEM` | `*string` | true     | PEM-encoded RSA public key. If provided, Persona encrypts the claim payload on redemption. Pass `nil` to receive plaintext JSON |

**Returns** — `*persona.CreateRelayResponse`

| Field                     | Type     | Description                                                                                                            |
| ------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------- |
| `RelayToken`              | `string` | Identifies the relay session. Pass to the subsequent `GenerateClaim` call                                              |
| `RelaySecret`             | `string` | Authenticates your server's calls on this session. Keep server-side — never expose to the client                       |
| `RelaySessionAccessToken` | `string` | Short-lived token that your server returns to the client for the Embedded Widget, Hosted Flow, iOS SDK, or Android SDK |

### `issuePrivacyPass`

Runs the full blind RSA issuance flow and returns a Privacy Pass token. The SDK handles all cryptography internally.

> **How does this work?**
>
> Curious how the Privacy Pass protocol works under the hood? See [Privacy Pass Protocol](/relay-privacy-pass-challenge) for a full walkthrough — relevant if
> you're implementing this yourself without the SDK.

Issuance depends only on the claim type — it is not tied to a relay, so it can run at any time, including before the relay is created.

> **Issue against the same claim type as your relay**
>
> The signing key the SDK fetches is determined by the claim type you pass, so each Privacy Pass is bound to that claim type. A pass can only redeem a relay created with the **same** claim type — if they don't match, the pass won't be able to redeem the claim.

#### Node.js

```typescript
const { privacyPassToken } = await persona.relays.issuePrivacyPass({
  claimType: "live_human_presence",
});
```

**Arguments** — `IssuePrivacyPassParams`

| Field       | Type     | Required | Description                                                                                                                                              |
| ----------- | -------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `claimType` | `string` | true     | The claim type to issue a Privacy Pass for. Must match the `claimType` of the relay you'll redeem against. See [Claim types](/relay-schemas#claim-types) |

**Returns** — `IssuePrivacyPassResponse`

| Field              | Type     | Description                                                                                                           |
| ------------------ | -------- | --------------------------------------------------------------------------------------------------------------------- |
| `privacyPassToken` | `string` | The issued Privacy Pass token. Hold this server-side and pass to `generateClaim` once the user completes verification |

#### Go

```go
issued, err := client.Relays.IssuePrivacyPass(ctx, persona.IssuePrivacyPassParams{
	ClaimType: "live_human_presence",
})
```

**Arguments** — `persona.IssuePrivacyPassParams`

| Field       | Type     | Required | Description                                                                                                                                              |
| ----------- | -------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ClaimType` | `string` | true     | The claim type to issue a Privacy Pass for. Must match the `ClaimType` of the relay you'll redeem against. See [Claim types](/relay-schemas#claim-types) |

**Returns** — `*persona.IssuePrivacyPassResponse`

| Field              | Type     | Description                                                                                                           |
| ------------------ | -------- | --------------------------------------------------------------------------------------------------------------------- |
| `PrivacyPassToken` | `string` | The issued Privacy Pass token. Hold this server-side and pass to `GenerateClaim` once the user completes verification |

### `generateClaim`

Redeems the Privacy Pass token against Persona and returns the claim payload.

#### Node.js

```typescript
const claim = await persona.relays.generateClaim({
  privacyPassToken,
  relayToken: session.relayToken,
  relaySecret: session.relaySecret,
});
```

If you created the relay session with an `encryptionKeyPem`, `claimPayload` will be a base64-encoded RSA-OAEP ciphertext. Decrypt it with your corresponding private key.

**Arguments** — `GenerateClaimParams`

| Field              | Type     | Required | Description                                                   |
| ------------------ | -------- | -------- | ------------------------------------------------------------- |
| `privacyPassToken` | `string` | true     | The Privacy Pass token from `persona.relays.issuePrivacyPass` |
| `relayToken`       | `string` | true     | The relay token from `persona.relays.create`                  |
| `relaySecret`      | `string` | true     | The relay secret from `persona.relays.create`                 |

**Returns** — `GenerateClaimResponse`

| Field           | Type      | Description                                                                                                                                                                    |
| --------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `claimPayload`  | `string`  | Plaintext JSON claim result, or base64-encoded RSA-OAEP ciphertext if `encryptionKeyPem` was set on session creation. See [Claim payload schema](/relay-schemas#claim-payload) |
| `tokenConsumed` | `boolean` | Whether the Privacy Pass token was consumed by this redemption. A consumed token cannot be redeemed again                                                                      |

#### Go

```go
claim, err := client.Relays.GenerateClaim(ctx, persona.GenerateClaimParams{
	PrivacyPassToken: privacyPassToken,
	RelayToken:       session.RelayToken,
	RelaySecret:      session.RelaySecret,
})
```

If you created the relay session with an `EncryptionKeyPEM`, `ClaimPayload` will be a base64-encoded RSA-OAEP ciphertext. Decrypt it with your corresponding private key.

**Arguments** — `persona.GenerateClaimParams`

| Field              | Type     | Required | Description                                                  |
| ------------------ | -------- | -------- | ------------------------------------------------------------ |
| `PrivacyPassToken` | `string` | true     | The Privacy Pass token from `client.Relays.IssuePrivacyPass` |
| `RelayToken`       | `string` | true     | The relay token from `client.Relays.Create`                  |
| `RelaySecret`      | `string` | true     | The relay secret from `client.Relays.Create`                 |

**Returns** — `*persona.GenerateClaimResponse`

| Field           | Type     | Description                                                                                                                                                                    |
| --------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `ClaimPayload`  | `string` | Plaintext JSON claim result, or base64-encoded RSA-OAEP ciphertext if `EncryptionKeyPEM` was set on session creation. See [Claim payload schema](/relay-schemas#claim-payload) |
| `TokenConsumed` | `bool`   | Whether the Privacy Pass token was consumed by this redemption. A consumed token cannot be redeemed again                                                                      |

## Error handling

#### Node.js

```typescript
import { PersonaError, BadRequestError, RateLimitedError } from '@persona/sdk-node';

try {
  const claim = await persona.relays.generateClaim({ ... });
} catch (err) {
  if (err instanceof RateLimitedError) {
    // retry with backoff
  } else if (err instanceof PersonaError) {
    console.error(err.statusCode, err.title, err.details);
  }
}
```

All Persona API errors and network failures throw a subtype of `PersonaError`.

| Property     | Type                                   | Description                           |
| ------------ | -------------------------------------- | ------------------------------------- |
| `statusCode` | `number`                               | HTTP status code from the Persona API |
| `title`      | `string`                               | Short description of the error        |
| `details`    | `string \| undefined`                  | Optional longer description           |
| `code`       | `string \| undefined`                  | Optional machine-readable error code  |
| `meta`       | `Record<string, unknown> \| undefined` | Optional additional metadata          |

#### Go

```go
import "errors"

claim, err := client.Relays.GenerateClaim(ctx, params)
if err != nil {
	var perr *persona.PersonaError
	if errors.As(err, &perr) {
		switch persona.HTTPStatusCode(perr.StatusCode) {
		case persona.StatusRateLimited:
			// retry with backoff
		default:
			log.Println(perr.StatusCode, perr.Title, perr.Details)
		}
	}
}
```

All Persona API errors and network failures return a `*persona.PersonaError`.

| Field        | Type             | Description                           |
| ------------ | ---------------- | ------------------------------------- |
| `StatusCode` | `int`            | HTTP status code from the Persona API |
| `Title`      | `string`         | Short description of the error        |
| `Details`    | `string`         | Optional longer description           |
| `Code`       | `string`         | Optional machine-readable error code  |
| `Meta`       | `map[string]any` | Optional additional metadata          |

See [Error responses](/relay-schemas#error-responses) for the raw JSON error shape returned by the API.