> For clean Markdown of any page, append .md to the page URL. > For a complete documentation index, see https://docs.withpersona.com/2020-05-18/relay-server-sdk-reference/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: '' }); ``` **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: ""}) ``` **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: "", }); ``` **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 := "" 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 \| 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. > Full API reference for the Persona Relay server-side SDK.