# Connected Apps Source: https://docs.userplane.io/account/connections View and manage MCP clients authorized to access your Userplane account The **Connections** tab in Account Settings lists every MCP client that has been authorized to access your account. From here you can review what each client can do, check its status, and revoke access at any time. ## What you can see Each connected app shows four pieces of information: | Column | What it means | | --------------- | -------------------------------------------------------------------------------- | | **Application** | The MCP client name and icon (Claude, Codex, Cursor, VS Code, etc.) | | **Permissions** | OAuth scopes granted — hover to see the full list | | **Status** | **Active** if the token is valid; **Inactive** if it has expired or been revoked | | **Authorized** | The date you approved the connection | ## Revoking access Go to [Account Settings > Connections](https://dash.userplane.io/_/account?tab=connections). Locate the app and click the three-dot menu on the right. Select **Revoke Access**. Type the application name in the confirmation field and click **Revoke Access**. The client loses access immediately. Revoking one client does not affect others. Each MCP client registers independently — Claude, Codex, Cursor, and VS Code hold separate authorizations. ## What happens after you revoke * The MCP client loses access to your account immediately. * Existing data the client retrieved is not affected. * Other connected clients continue working without interruption. * To reconnect, add the server URL to the client again and re-authorize through the OAuth flow. ## No connected apps If you haven't authorized any MCP clients yet, the tab shows an empty state. Connect Claude, Codex, Cursor, VS Code, or ChatGPT to your Userplane workspace OAuth 2.1 flow, PKCE, token scoping, and per-client registration ## What the MCP integration gives you Once connected, your AI client can analyze recordings, surface console errors and network failures, manage recording links, and browse workspace data — directly in the conversation. Fetch console logs, network requests, and user actions from any recording Video playback, log viewers, and network inspectors rendered inside your AI client Create and track recording links for customer issues Browse workspaces, projects, and your profile # Leaving a Workspace Source: https://docs.userplane.io/account/leaving-workspace How to leave a workspace and what happens to your data You can leave a workspace at any time if you no longer need access. Leaving is immediate and removes you from the workspace's member list. ## How to leave Go to [Account Settings](https://dash.userplane.io/_/account). Find the workspace you want to leave. Click **Leave Workspace** and confirm. ## What happens when you leave * You lose access to the workspace immediately. * Your past recordings and links remain in the workspace — they are not deleted. * Other members can still view recordings and links you created. * You are removed from the workspace member list. ## Owner requirement If you are the workspace **owner**, you must transfer ownership to another member before you can leave. A workspace must always have an owner. To transfer ownership: 1. Go to [Workspace Settings > Team](https://dash.userplane.io/_/settings?tab=team). 2. Select the member you want to make owner. 3. Change their role to **Owner**. 4. Once ownership is transferred, you can leave the workspace. ## Rejoining After leaving, you can rejoin the workspace depending on the [invite policy](/workspace/invite-policies): * **Private** — you need a new invitation. * **Whitelisted** — you can rejoin directly if your email domain matches. * **Protected** — you can request access again. # Notification Channels & Preferences Source: https://docs.userplane.io/account/notifications Configure how and where you receive notifications from Userplane Userplane sends notifications to keep you informed about recordings, usage, and workspace activity. You can control which notification types you receive and, for incoming recordings, which channels deliver them. ## Notification types | Notification | Description | Configurable by | | ----------------------- | ------------------------------------------------------------------------------------------------ | -------------------- | | **Feature highlights** | Updates about new features and product improvements. | Owner, Admin, Member | | **Usage notifications** | Alerts when your workspace approaches or exceeds usage limits. | Admin, Member | | **Incoming recordings** | Notifications when a new recording is submitted — delivered via one or more channels you choose. | Owner, Admin, Member | | **Admin notifications** | Workspace administration events (member joins, access requests, role changes). | Admin | ## Notification channels Incoming recording notifications can be delivered through any combination of the following channels: | Channel | What it does | Requires | | ------------ | ------------------------------------------------------------------------------ | ------------------------------------------ | | **Email** | Sends an email when a recording arrives. | Nothing — always available. | | **Slack** | Sends a direct message in Slack when a recording arrives. | Active Slack integration + matching email. | | **Intercom** | Posts an admin note to the Intercom conversation the recording link came from. | Active Intercom integration. | | **Zendesk** | Posts an internal note to the Zendesk ticket the recording link came from. | Active Zendesk integration. | Email is always available. Slack, Intercom, and Zendesk channels appear only when your workspace admin has connected the respective integration. Intercom and Zendesk notifications are context-tied — they only fire when the recording link was created from a conversation or ticket in that tool. Slack and email notifications fire for all incoming recordings regardless of where the link was created. ## Configuring preferences Go to [Account Settings > Notifications](https://dash.userplane.io/_/account?tab=workspaces). Toggle each notification type on or off. Changes are saved automatically. Under **Incoming Recordings**, toggle each channel individually. Only channels with active integrations are shown alongside Email. Enable the channels you want — you can use more than one at a time. Only toggles your role can control are shown. If a notification type is missing, it is either always enabled or not applicable for your role. ## Role-based rules * **Usage notifications** — Owners always receive these and cannot opt out. Admins and members can toggle them freely. * **Admin notifications** — Owners always receive these and cannot opt out. Members never receive them. Only admins see this toggle and can opt in or out. * **Feature highlights** and **Incoming recordings** — All roles can toggle these. ## Defaults | Notification | Default state | | ----------------------- | ------------------------------------------------------------- | | **Feature highlights** | Enabled for all roles | | **Usage notifications** | Enabled for all roles | | **Incoming recordings** | All channels disabled for all roles (opt-in) | | **Admin notifications** | Enabled for owners (locked), disabled for admins (can opt in) | Incoming recording notifications are opt-in across all channels. Enable the channels you want to use — each one can be toggled independently. ## Role changes When a member's role changes, notification preferences that the new role cannot control are automatically set to their correct value. For example, promoting someone to owner forces usage and admin notifications on. Demoting an owner to member forces admin notifications off. Preferences the new role can control are left unchanged. ## Channel-specific guides * [Slack Notifications](/integrations/slack-notifications) — how Slack DMs work and how to enable them. * [Intercom Notifications](/integrations/intercom-notifications) — how admin notes are posted to Intercom conversations. * [Zendesk Notifications](/integrations/zendesk-notifications) — how internal notes are posted to Zendesk tickets. # Updating Your Profile Source: https://docs.userplane.io/account/profile Change your display name and other profile settings Your profile settings control how you appear to other workspace members and to customers who receive recording links from you. ## Changing your display name Your display name is shown to: * Other workspace members in the team list and activity feeds. * Customers in recording links (the "requested by" field). Go to [Account Settings > Profile](https://dash.userplane.io/_/account). Edit the **Display name** field and save your changes. ## Profile settings From Account Settings you can also manage: * **Email address** — the email associated with your Userplane account. * **Workspace memberships** — view and manage the workspaces you belong to. See [Leaving a Workspace](/account/leaving-workspace). * **Notification preferences** — control which notifications you receive and which channels deliver them. See [Notification Channels & Preferences](/account/notifications). # API Keys Source: https://docs.userplane.io/api/api-keys Create, manage, and rotate Userplane API keys API keys provide programmatic access to the Userplane API. Each key is scoped to the user who created it and shares that user's workspace permissions. You can manage API keys from the [Developers tab](https://dash.userplane.io/_/account?tab=developers) of your account settings. API Keys in the Developers tab ## Key limits | Limit | Value | | ---------------------- | ------- | | Maximum keys per user | 3 | | Maximum key expiration | 90 days | | Key prefix | `uspl_` | ## Rate limits Rate limits are applied per-user, shared across all API keys for that user: | Window | Limit | | ---------- | --------------- | | Per minute | 120 requests | | Per day | 10,000 requests | See [Rate Limits](/api/rate-limits) for more details. ## Managing your keys Generate a new API key with optional name and expiration View your keys, check status, and access key actions Invalidate a key and generate a replacement Permanently delete a key # Create API Key Source: https://docs.userplane.io/api/api-keys/create-api-key Create a new API key from the Userplane dashboard Create an API key to start accessing the Userplane API programmatically. Each user can have up to 3 API keys. Navigate to [Account Settings](https://dash.userplane.io/_/account?tab=developers) and select the **Developers** tab. If you don't have any keys yet, you'll see the empty state: API Keys empty state Click the **+ Create API Key** button. You'll be prompted to configure your key: * **Name** (optional) — A label to identify the key (e.g., "CI Pipeline", "Production API") * **Expiration** — Choose from: No expiration, 7 days, 30 days, 60 days, or 90 days After creation, the full API key is displayed. Copy it and store it securely. The full API key is only shown once. If you lose it, you'll need to create a new key or [rotate](/api/api-keys/rotate-api-key) the existing one. Your new key will appear in the keys list: API Keys list with keys ## Key limits | Limit | Value | | --------------------- | ------- | | Maximum keys per user | 3 | | Maximum expiration | 90 days | ## Expiration options | Option | Duration | | ------------- | ------------------- | | No expiration | Key does not expire | | 7 days | 604,800 seconds | | 30 days | 2,592,000 seconds | | 60 days | 5,184,000 seconds | | 90 days | 7,776,000 seconds | ## Next steps Once you have your key, authenticate API requests by including it as a Bearer token: ```bash theme={null} curl https://api.userplane.io/api/v1/public/me \ -H "Authorization: Bearer uspl_your_api_key" ``` See the [Quickstart](/api/quickstart) to make your first API call. ## Related View and manage your existing keys Replace a key without losing your slot Make your first API call with your new key How to use your key in API requests # Manage API Keys Source: https://docs.userplane.io/api/api-keys/list-api-keys View and manage your API keys from the Userplane dashboard View all your API keys, check their status, and access key management actions from the Developers tab. ## Viewing your keys Navigate to [Account Settings](https://dash.userplane.io/_/account?tab=developers) and select the **Developers** tab. Your keys are listed in a table showing: | Column | Description | | ----------- | ------------------------------------------------------------- | | **Name** | The label you assigned when creating the key | | **API Key** | Masked key showing the `uspl_` prefix and last few characters | | **Expires** | Expiration date, or "Never" for keys with no expiration | | **Status** | Current status (Active or Disabled) | API Keys list ## Key actions Click the three-dot menu next to any key to access management actions: API Key options menu | Action | Description | | -------------- | ------------------------------------------------------------------------------------------------------ | | **Rotate Key** | Invalidate the current key and generate a new one. See [Rotate API Key](/api/api-keys/rotate-api-key). | | **Revoke Key** | Permanently delete the key. See [Revoke API Key](/api/api-keys/revoke-api-key). | API keys are masked for security. You cannot view the full key after creation. If you've lost a key, [rotate](/api/api-keys/rotate-api-key) it to generate a new one. ## Related Generate a new API key Invalidate a key and generate a replacement Permanently delete a key Rate limits shared across all your keys # Revoke API Key Source: https://docs.userplane.io/api/api-keys/revoke-api-key Permanently delete an API key Revoking a key permanently deletes it. Any integrations or scripts using the key will stop working immediately. This action cannot be undone. Navigate to [Account Settings](https://dash.userplane.io/_/account?tab=developers), find the key you want to revoke, and click the three-dot menu. Select **Revoke Key**. Key options with Revoke Key selected A confirmation dialog appears. Type **REVOKE** to confirm, then click **Revoke Key**. Revoke API Key confirmation dialog This is permanent. Any requests using this key will immediately return `401 API_KEY_INVALID`. ## When to revoke a key * The key has been **exposed** in a public repository, log, or client-side code * The key is **no longer needed** (e.g., decommissioning an integration) * You want to **free up a slot** to create a new key (maximum 3 keys per user) If you need to replace a key rather than just delete it, use [Rotate API Key](/api/api-keys/rotate-api-key) instead. Rotation revokes the old key and generates a new one in a single step. ## Related Replace a key instead of just deleting it Generate a new key after revoking View all your keys and their status Secure key storage and rotation strategies # Rotate API Key Source: https://docs.userplane.io/api/api-keys/rotate-api-key Invalidate an existing API key and generate a new replacement Rotating a key invalidates the current key immediately and generates a new one. Use this when a key has been compromised or when you need to refresh credentials. Navigate to [Account Settings](https://dash.userplane.io/_/account?tab=developers), find the key you want to rotate, and click the three-dot menu. Select **Rotate Key**. Key options with Rotate Key selected In the rotation dialog, configure your replacement key: * **Name** (optional) — A label to identify the new key * **Expiration** — Choose from: No expiration, 7 days, 30 days, 60 days, or 90 days Rotate API Key dialog Click **Rotate Key** to proceed. The old key is immediately invalidated. Your new key is displayed in the success dialog. API Key Rotated success dialog Copy the new key now. It will not be shown again after you close this dialog. Click **Done** to return to the keys list. ## What happens during rotation * The old key is **permanently invalidated** — any requests using it will immediately return `401 API_KEY_INVALID` * A new key is generated with the prefix `uspl_` and the configuration you specified * The new key counts toward your 3-key limit (the old key no longer counts) To avoid downtime, update your integration with the new key before the rotation takes effect. Consider using environment variables or a secret manager so key updates require only one change. ## Related Permanently delete a key without replacement View all your keys and their status Create a new key from scratch Plan for key rotation with zero downtime # Authentication Source: https://docs.userplane.io/api/authentication Authenticate API requests with Bearer tokens using your Userplane API key All API requests must include a valid API key in the `Authorization` header as a Bearer token. ## Bearer token Include your API key in the `Authorization` header with every request: ```bash curl theme={null} curl https://api.userplane.io/api/v1/public/me \ -H "Authorization: Bearer uspl_your_api_key" ``` ```javascript JavaScript theme={null} const response = await fetch('https://api.userplane.io/api/v1/public/me', { headers: { Authorization: 'Bearer uspl_your_api_key', }, }); const data = await response.json(); ``` ```python Python theme={null} import requests response = requests.get( "https://api.userplane.io/api/v1/public/me", headers={"Authorization": "Bearer uspl_your_api_key"}, ) data = response.json() ``` ## API key format All Userplane API keys use the `uspl_` prefix. Keys are bound to the user who created them and carry the same permissions as that user across all their workspaces. API keys carry the same permissions as the user who created them. Never expose keys in client-side code, public repositories, or browser requests. ## User-scoped vs workspace-scoped endpoints The API has two types of endpoints based on how authorization is handled: | Type | Path pattern | Authorization | | ---------------- | ----------------------------------- | -------------------------------------------------- | | User-scoped | `/public/me`, `/public/workspaces` | Verifies API key validity only | | Workspace-scoped | `/public/workspace/{workspaceId}/*` | Verifies API key validity and workspace membership | Workspace-scoped endpoints return a `403 NOT_A_MEMBER` error if the authenticated user is not an active member of the specified workspace. ## Getting an API key You can create API keys from the [Developers section](https://dash.userplane.io/_/account?tab=developers) of your account settings, or programmatically via the [API Key Management](/api/api-keys/create-api-key) endpoints. See [API Keys](/api/api-keys) for details on creating, rotating, and managing your keys. ## Related Create, rotate, and manage your API keys Make your first authenticated API call Authentication error codes and troubleshooting Per-user rate limits across all API keys # Best Practices Source: https://docs.userplane.io/api/best-practices Recommendations for building reliable integrations with the Userplane API Follow these guidelines to build robust and maintainable integrations with the Userplane API. ## Store keys securely Keep API keys in environment variables or a secret manager. Never commit keys to version control or embed them in client-side code. If a key is accidentally exposed, [revoke it immediately](https://dash.userplane.io/_/account?tab=developers) and create a new one. ## Handle errors gracefully * **Retry on `429` and `5xx`** — these are transient errors. Use exponential backoff. * **Do not retry on `4xx` (except `429`)** — these indicate issues that require a code change. * **Check `error` codes programmatically** — match on codes like `NOT_A_MEMBER` or `LINK_NOT_FOUND` rather than parsing message strings. See [Error Handling](/api/error-handling) for the full error reference. ## Use pagination Do not assume all results fit in a single page. Always check the `hasMore` field and iterate through pages when processing large datasets. See [Pagination](/api/pagination) for details. ## Plan for key rotation Design your integration so that API key rotation causes no downtime. Store the key in a single, easily updatable location (environment variable, secret manager) so rotating it requires only one change. ## Handle presigned URLs correctly Recording resource endpoints (video, thumbnail, console log, network log, action) return presigned URLs that expire after a limited time. Always fetch these URLs on demand and do not cache or persist them. See [Recording Resources](/api/recordings/get-recording-resources) for details. ## Understand soft deletes Deleted recordings and links return `410 Gone` rather than `404 Not Found`. This confirms the resource previously existed but has been removed. Handle both status codes appropriately in your integration. ## CORS considerations The public API endpoints allow requests from any origin (`Access-Control-Allow-Origin: *`) with credentials disabled. The API is designed for server-to-server usage. Avoid making API calls directly from customer-facing browser applications. ## Related Rate limit thresholds and retry strategies Error codes and response format reference Secure your API requests with Bearer tokens Handle presigned URLs for recording assets # Error Handling Source: https://docs.userplane.io/api/error-handling API error codes, HTTP status codes, and error response format The API uses standard HTTP status codes and returns structured error responses to help you handle failures programmatically. ## Error response format Error responses include an `error` code for programmatic handling, a human-readable `message`, and a link to the relevant documentation: ```json theme={null} { "error": "API_KEY_REQUIRED", "message": "API key is required. Include it as Authorization: Bearer . See https://docs.userplane.io/api/authentication" } ``` ## Authentication errors Returned when the API key is missing, invalid, or expired. | Error code | Status | Message | | ------------------ | ------ | ------------------------------------------------------------------------- | | `API_KEY_REQUIRED` | 401 | API key is required. Include it as Authorization: Bearer \. | | `API_KEY_INVALID` | 401 | The provided API key is not valid. Verify the key and try again. | | `API_KEY_EXPIRED` | 401 | The API key has expired. Rotate or create a new key. | ## Authorization errors Returned when the authenticated user lacks access to the requested resource. | Error code | Status | Message | | ------------------------------ | ------ | ------------------------------------------------------- | | `API_KEY_DISABLED` | 403 | The API key is disabled. Enable it or create a new key. | | `NOT_A_MEMBER` | 403 | The API key owner is not a member of this workspace. | | `LINK_DELETE_FORBIDDEN` | 403 | You do not have permission to delete this link. | | `RECORDING_DELETE_FORBIDDEN` | 403 | You do not have permission to delete this recording. | | `RECORDING_WORKSPACE_MISMATCH` | 403 | Recording does not belong to this workspace. | ## Rate limiting | Error code | Status | Message | | -------------- | ------ | ------------------------------------------------------------- | | `RATE_LIMITED` | 429 | Rate limit exceeded. Wait and retry with exponential backoff. | ## Service errors | Error code | Status | Message | | --------------------- | ------ | -------------------------------------------------------------------------------- | | `SERVICE_UNAVAILABLE` | 503 | API key verification service is temporarily unavailable. Please try again later. | See [Rate Limits](/api/rate-limits) for details on limits and retry strategies. ## Resource errors Returned when the requested resource does not exist or has been deleted. | Error code | Status | Message | | ---------------------- | ------ | ---------------------------------- | | `LINK_NOT_FOUND` | 404 | Link not found | | `RECORDING_NOT_FOUND` | 404 | Recording not found | | `PROJECT_NOT_FOUND` | 404 | Project not found | | `WORKSPACE_NOT_FOUND` | 404 | Workspace not found | | `LINK_GONE` | 410 | Link has been deleted | | `RECORDING_GONE` | 410 | Recording has already been deleted | | `PROJECT_GONE` | 410 | Project has been deleted | | `LINK_PROJECT_DELETED` | 410 | Project has been deleted | A `410 Gone` response indicates the resource was soft-deleted. Unlike `404 Not Found`, this confirms the resource previously existed. ## Mutation errors Returned when a create, update, or delete operation fails. | Error code | Status | Message | | -------------------------------- | ------ | -------------------------------------- | | `LINK_CREATE_FAILED` | 500 | Failed to create link | | `LINK_UPDATE_FAILED` | 500 | Failed to update link | | `LINK_DELETE_FAILED` | 500 | Failed to delete link | | `RECORDING_DELETE_FAILED` | 500 | Failed to delete recording | | `LINK_DEFAULT_PROJECT_NOT_FOUND` | 404 | No default project found for workspace | | `PLAN_LIMIT_EXCEEDED` | 403 | Plan limit exceeded | ## API key management errors These errors apply to the [API Key Management](/api/api-keys/create-api-key) endpoints, which use session authentication. | Error code | Status | Message | | ------------------------ | ------ | --------------------------------------------------------- | | `UNAUTHORIZED` | 401 | You must be logged in to manage API keys. | | `API_KEY_ALREADY_EXISTS` | 409 | You already have an API key. Revoke or rotate it instead. | | `API_KEY_NOT_FOUND` | 404 | No API key found. | | `API_KEY_CREATE_FAILED` | 500 | Failed to create API key. | | `API_KEY_REVOKE_FAILED` | 500 | Failed to revoke API key. | | `API_KEY_ROTATE_FAILED` | 500 | Failed to rotate API key. | ## Best practices * **Check HTTP status first** — use the status code to determine the error category (4xx = client error, 5xx = server error). * **Use the `error` field for programmatic handling** — match on error codes like `API_KEY_EXPIRED` or `RATE_LIMITED` in your code. * **Use the `message` field for logging** — the message provides human-readable context for debugging. * **Retry on 429 and 5xx** — these are transient and may succeed on retry with backoff. * **Do not retry on 4xx (except 429)** — these indicate a client-side issue that needs to be fixed. ## Related Rate limit thresholds and retry strategies API key authentication and authorization scopes Page-based pagination for list endpoints Build reliable integrations that handle errors gracefully # API Overview Source: https://docs.userplane.io/api/introduction Programmatic access to Userplane workspaces, recordings, and links The Userplane API provides programmatic access to your workspaces, recording links, and recordings. Use it to build integrations, automate workflows, or access data from your Userplane account. ## Base URL All API requests are made to: ``` https://api.userplane.io/api/v1 ``` ## What you can do Retrieve your profile and aggregate stats List and retrieve workspace details Create, update, list, and delete recording links List, retrieve, and manage recordings and their resources ## Authentication All requests require a Bearer token in the `Authorization` header. API keys start with the `uspl_` prefix. ```bash theme={null} curl https://api.userplane.io/api/v1/public/me \ -H "Authorization: Bearer uspl_your_api_key" ``` See [Authentication](/api/authentication) for details. ## Response format All endpoints return a consistent response envelope: ```json Success response theme={null} { "message": "User profile retrieved successfully", "data": { "user": { "id": "user_abc123", "name": "Jane Doe", "email": "jane@example.com" } } } ``` ```json Error response theme={null} { "error": "API_KEY_REQUIRED", "message": "API key required. Use Authorization: Bearer " } ``` ## Next steps Learn how to authenticate API requests Create and manage your API keys Make your first API call in under two minutes Understand error codes and response format # Create Link Source: https://docs.userplane.io/api/links/create-link POST /public/workspace/{workspaceId}/links Create a new recording link for a domain Creates a new recording link in the specified workspace. ## Errors | Error code | Status | Description | | -------------------------------- | ------ | -------------------------------------- | | `LINK_DEFAULT_PROJECT_NOT_FOUND` | 404 | No default project found for workspace | | `LINK_PROJECT_DELETED` | 410 | The specified project has been deleted | | `LINK_CREATE_FAILED` | 500 | Failed to create the link | | `PLAN_LIMIT_EXCEEDED` | 403 | Workspace plan limit exceeded | Also returns [common authentication errors](/api/error-handling#authentication-errors). ## Related View all links in a workspace Retrieve the created link by ID Update link settings after creation Find project IDs for link creation # Delete Link Source: https://docs.userplane.io/api/links/delete-link DELETE /public/workspace/{workspaceId}/links/{linkId} Delete a recording link Soft-deletes a recording link. Only the link creator or a workspace admin/owner can delete a link. This is a soft delete. The link data is preserved but becomes inaccessible. Subsequent requests for this link return `410 Gone`. ## Errors | Error code | Status | Description | | ----------------------- | ------ | ---------------------------------------------- | | `LINK_NOT_FOUND` | 404 | Link does not exist | | `LINK_GONE` | 410 | Link has already been deleted | | `LINK_DELETE_FORBIDDEN` | 403 | You do not have permission to delete this link | | `LINK_DELETE_FAILED` | 500 | Failed to delete the link | Also returns [common authentication errors](/api/error-handling#authentication-errors). ## Related View all active links in the workspace Create a new recording link Retrieve link details by ID Understand 410 Gone and other error codes # Get Link Source: https://docs.userplane.io/api/links/get-link GET /public/workspace/{workspaceId}/links/{linkId} Retrieve recording link details by ID Returns details for a specific recording link. ## Errors | Error code | Status | Description | | ---------------- | ------ | --------------------- | | `LINK_NOT_FOUND` | 404 | Link does not exist | | `LINK_GONE` | 410 | Link has been deleted | Also returns [common authentication errors](/api/error-handling#authentication-errors). ## Related Update this link's settings Soft-delete this link View all links in the workspace Recordings captured from this link # List Links Source: https://docs.userplane.io/api/links/list-links GET /public/workspace/{workspaceId}/links Retrieve a paginated list of recording links with optional filters Returns a paginated list of recording links in the specified workspace with optional filtering and sorting. ## Errors This endpoint returns [common authentication errors](/api/error-handling#authentication-errors). ## Related Create a new recording link Retrieve details for a specific link Filter recordings by link ID Page-based pagination for list endpoints # Update Link Source: https://docs.userplane.io/api/links/update-link PUT /public/workspace/{workspaceId}/links/{linkId} Update recording link settings Updates the settings of an existing recording link. All body fields are optional — only include the fields you want to change. ## Errors | Error code | Status | Description | | -------------------- | ------ | ------------------------- | | `LINK_NOT_FOUND` | 404 | Link does not exist | | `LINK_GONE` | 410 | Link has been deleted | | `LINK_UPDATE_FAILED` | 500 | Failed to update the link | Also returns [common authentication errors](/api/error-handling#authentication-errors). ## Related Retrieve link details before or after update Soft-delete a link you no longer need Create a new recording link View all links in the workspace # Pagination Source: https://docs.userplane.io/api/pagination Page-based pagination for list endpoints All list endpoints in the Userplane API use page-based pagination. ## Query parameters | Parameter | Type | Default | Description | | ---------- | ------- | ------- | ----------------------- | | `page` | integer | `1` | Page number (1-indexed) | | `per_page` | integer | Varies | Items per page | ## Default limits by resource | Resource | Default `per_page` | Max `per_page` | | ---------- | ------------------ | -------------- | | Workspaces | 10 | 20 | | Links | 10 | 20 | | Recordings | 10 | 20 | | Projects | 5 | 10 | ## Pagination metadata Every list endpoint returns a `pagination` object alongside the results: ```json theme={null} { "message": "Links retrieved successfully", "data": { "workspaceId": "ws_abc123", "links": [...], "pagination": { "page": 1, "perPage": 10, "total": 47, "totalPages": 5, "hasMore": true } } } ``` | Field | Type | Description | | ------------ | ------- | ---------------------------- | | `page` | integer | Current page number | | `perPage` | integer | Items per page | | `total` | integer | Total items across all pages | | `totalPages` | integer | Total number of pages | | `hasMore` | boolean | Whether more pages exist | ## Iterating through pages Use the `hasMore` field to iterate through all pages: ```javascript JavaScript theme={null} const API_BASE = "https://api.userplane.io/api/v1"; async function fetchAllRecordings(workspaceId, apiKey) { const recordings = []; let page = 1; let hasMore = true; while (hasMore) { const response = await fetch( `${API_BASE}/public/workspace/${workspaceId}/recordings?page=${page}&per_page=20`, { headers: { Authorization: `Bearer ${apiKey}` } } ); const { data } = await response.json(); recordings.push(...data.recordings); hasMore = data.pagination.hasMore; page++; } return recordings; } ``` ```python Python theme={null} import requests API_BASE = "https://api.userplane.io/api/v1" def fetch_all_recordings(workspace_id, api_key): recordings = [] page = 1 has_more = True while has_more: response = requests.get( f"{API_BASE}/public/workspace/{workspace_id}/recordings", headers={"Authorization": f"Bearer {api_key}"}, params={"page": page, "per_page": 20}, ) data = response.json()["data"] recordings.extend(data["recordings"]) has_more = data["pagination"]["hasMore"] page += 1 return recordings ``` ## Related Paginated list of recording links with filters Paginated list of recordings with filters Paginated list of your workspaces Paginated list of projects in a workspace # Get Project Source: https://docs.userplane.io/api/projects/get-project GET /public/workspace/{workspaceId}/projects/{projectId} Retrieve project details by ID Returns details for a specific project in the workspace. ## Errors | Error code | Status | Description | | ------------------- | ------ | ------------------------ | | `PROJECT_NOT_FOUND` | 404 | Project does not exist | | `PROJECT_GONE` | 410 | Project has been deleted | Also returns [common authentication errors](/api/error-handling#authentication-errors). ## Related Paginated list of all projects in a workspace Recording links filtered by project Recordings filtered by project Workspace details for this project # List Projects Source: https://docs.userplane.io/api/projects/list-projects GET /public/workspace/{workspaceId}/projects Retrieve a paginated list of projects in a workspace Returns a paginated list of projects in the specified workspace. ## Errors | Error code | Status | Description | | ----------- | ------ | ------------------------ | | `FORBIDDEN` | 403 | Insufficient permissions | Also returns [common authentication errors](/api/error-handling#authentication-errors). ## Related Retrieve details for a specific project Filter recording links by project Filter recordings by project Page-based pagination for list endpoints # Quickstart Source: https://docs.userplane.io/api/quickstart Make your first Userplane API call in under two minutes This guide walks you through making your first API calls to retrieve your profile, list your workspaces, and fetch recordings. ## Prerequisites * A Userplane account * An API key from the [Developers section](https://dash.userplane.io/_/account?tab=developers) of your account settings ## Make your first API call Verify your API key works by retrieving your user profile: ```bash curl theme={null} curl https://api.userplane.io/api/v1/public/me \ -H "Authorization: Bearer uspl_your_api_key" ``` ```javascript JavaScript theme={null} const response = await fetch("https://api.userplane.io/api/v1/public/me", { headers: { Authorization: "Bearer uspl_your_api_key" }, }); const { data } = await response.json(); console.log(data.user.name); ``` ```python Python theme={null} import requests response = requests.get( "https://api.userplane.io/api/v1/public/me", headers={"Authorization": "Bearer uspl_your_api_key"}, ) data = response.json()["data"] print(data["user"]["name"]) ``` Example response: ```json theme={null} { "message": "User profile retrieved successfully", "data": { "user": { "id": "user_abc123", "name": "Jane Doe", "email": "jane@example.com", "emailVerified": true, "image": null, "createdAt": "2025-01-15T10:30:00.000Z" }, "stats": { "workspaces": 2, "recordings": 48, "links": 15 } } } ``` Retrieve the workspaces you belong to: ```bash curl theme={null} curl https://api.userplane.io/api/v1/public/workspaces \ -H "Authorization: Bearer uspl_your_api_key" ``` ```javascript JavaScript theme={null} const response = await fetch( "https://api.userplane.io/api/v1/public/workspaces", { headers: { Authorization: "Bearer uspl_your_api_key" } } ); const { data } = await response.json(); const workspaceId = data.workspaces[0].workspaceId; ``` ```python Python theme={null} response = requests.get( "https://api.userplane.io/api/v1/public/workspaces", headers={"Authorization": "Bearer uspl_your_api_key"}, ) workspace_id = response.json()["data"]["workspaces"][0]["workspaceId"] ``` Note the `workspaceId` from the response — you'll need it for workspace-scoped endpoints. Fetch recordings from your workspace using the `workspaceId` from the previous step: ```bash curl theme={null} curl https://api.userplane.io/api/v1/public/workspace/ws_abc123/recordings \ -H "Authorization: Bearer uspl_your_api_key" ``` ```javascript JavaScript theme={null} const response = await fetch( `https://api.userplane.io/api/v1/public/workspace/${workspaceId}/recordings`, { headers: { Authorization: "Bearer uspl_your_api_key" } } ); const { data } = await response.json(); console.log(`Found ${data.pagination.total} recordings`); ``` ```python Python theme={null} response = requests.get( f"https://api.userplane.io/api/v1/public/workspace/{workspace_id}/recordings", headers={"Authorization": "Bearer uspl_your_api_key"}, ) data = response.json()["data"] print(f"Found {data['pagination']['total']} recordings") ``` ## Next steps Manage recording links Download videos, thumbnails, and logs Understand API rate limits Handle errors gracefully # Rate Limits Source: https://docs.userplane.io/api/rate-limits Understand API rate limits — 120 requests per minute and 10,000 per day The Userplane API enforces rate limits to ensure fair usage and platform stability. ## Default limits | Window | Limit | | ---------- | --------------- | | Per minute | 120 requests | | Per day | 10,000 requests | ## How limits are applied Rate limits are applied **per-user**, shared across all API keys belonging to that user. If you have multiple API keys, they all count toward the same limits. Rate limits are **not** per-workspace. A single request to any endpoint consumes from the same quota regardless of which workspace is being accessed. ## Handling rate limit errors When you exceed the rate limit, the API returns a `429` status code: ```json theme={null} { "error": "RATE_LIMITED", "message": "Rate limit exceeded. Wait and retry with exponential backoff. See https://docs.userplane.io/api/rate-limits" } ``` ## Retry with backoff Implement exponential backoff when you receive a `429` response: ```javascript JavaScript theme={null} async function fetchWithRetry(url, options, maxRetries = 3) { for (let attempt = 0; attempt <= maxRetries; attempt++) { const response = await fetch(url, options); if (response.status !== 429) return response; const delay = Math.pow(2, attempt) * 1000; await new Promise((resolve) => setTimeout(resolve, delay)); } throw new Error("Rate limit exceeded after retries"); } ``` ```python Python theme={null} import time import requests def fetch_with_retry(url, headers, max_retries=3): for attempt in range(max_retries + 1): response = requests.get(url, headers=headers) if response.status_code != 429: return response delay = (2 ** attempt) time.sleep(delay) raise Exception("Rate limit exceeded after retries") ``` Rate limits are subject to change. Design your integration with retry logic from the start. ## Related Full error code reference including rate limit errors Build reliable integrations with retry logic API key authentication and scoping Efficiently paginate through large result sets # Delete Recording Source: https://docs.userplane.io/api/recordings/delete-recording DELETE /public/workspace/{workspaceId}/recordings/{recordingId} Soft-delete a recording Soft-deletes a recording. Only the recording's link creator or a workspace admin/owner can delete a recording. This is a soft delete. The recording data is preserved but becomes inaccessible. Subsequent requests for this recording return `410 Gone`. ## Errors | Error code | Status | Description | | ------------------------------ | ------ | --------------------------------------------------- | | `RECORDING_NOT_FOUND` | 404 | Recording does not exist | | `RECORDING_GONE` | 410 | Recording has already been deleted | | `RECORDING_WORKSPACE_MISMATCH` | 403 | Recording does not belong to this workspace | | `RECORDING_DELETE_FORBIDDEN` | 403 | You do not have permission to delete this recording | | `RECORDING_DELETE_FAILED` | 500 | Failed to delete the recording | Also returns [common authentication errors](/api/error-handling#authentication-errors). ## Related View all active recordings in the workspace Retrieve recording details before deletion Soft-delete the associated recording link Understand 410 Gone and other error codes # Get Recording Source: https://docs.userplane.io/api/recordings/get-recording GET /public/workspace/{workspaceId}/recordings/{recordingId} Retrieve complete recording details Returns full details for a specific recording, including metadata about the link, creator, project, domain, and capture status. ## Errors | Error code | Status | Description | | --------------------- | ------ | ---------------------------------- | | `RECORDING_NOT_FOUND` | 404 | Recording does not exist | | `RECORDING_GONE` | 410 | Recording has already been deleted | Also returns [common authentication errors](/api/error-handling#authentication-errors). ## Related Download video, thumbnail, and log files Soft-delete this recording View all recordings in the workspace Details for the link that captured this recording # Recording Resources Source: https://docs.userplane.io/api/recordings/get-recording-resources Retrieve presigned URLs for recording video, thumbnail, and log files These endpoints return presigned URLs for a recording's associated resources. All five endpoints share the same path parameters and response shape. Presigned URLs are temporary and expire after a limited time. Always fetch them on demand — do not cache or persist them. ## Endpoints | Endpoint | Description | | -------------------------------------------------------------------------- | ------------------------- | | `GET /public/workspace/{workspaceId}/recordings/{recordingId}/video` | Recording video | | `GET /public/workspace/{workspaceId}/recordings/{recordingId}/thumbnail` | Recording thumbnail image | | `GET /public/workspace/{workspaceId}/recordings/{recordingId}/console-log` | Browser console logs | | `GET /public/workspace/{workspaceId}/recordings/{recordingId}/network-log` | Network request logs | | `GET /public/workspace/{workspaceId}/recordings/{recordingId}/action` | Client action replay data | ## Path parameters The workspace ID The recording ID ## Response Status message Recording ID Presigned URL for the resource. `null` if the resource is not available. Status message describing the resource availability MIME type of the resource Video sources with URL and MIME type (video endpoint only) Source URL MIME type File size in bytes Duration in milliseconds (video only) Whether this resource type was captured for this recording ## Example ```bash curl theme={null} curl https://api.userplane.io/api/v1/public/workspace/ws_abc123/recordings/rec_abc123/video \ -H "Authorization: Bearer uspl_your_api_key" ``` ```javascript JavaScript theme={null} const response = await fetch( "https://api.userplane.io/api/v1/public/workspace/ws_abc123/recordings/rec_abc123/video", { headers: { Authorization: "Bearer uspl_your_api_key" } } ); const { data } = await response.json(); // Use data.resource.url or data.resource.sources for playback ``` ```json Response theme={null} { "message": "Recording video retrieved successfully", "data": { "recordingId": "rec_abc123", "resource": { "url": "https://s3.amazonaws.com/userplane-recordings/rec_abc123/video.webm?X-Amz-...", "message": "Video available", "type": "video/webm", "sources": [ { "url": "https://s3.amazonaws.com/userplane-recordings/rec_abc123/video.webm?X-Amz-...", "type": "video/webm" } ], "size": 2456789, "durationMs": 45200, "captureEnabled": true } } } ``` ```bash theme={null} curl https://api.userplane.io/api/v1/public/workspace/ws_abc123/recordings/rec_abc123/thumbnail \ -H "Authorization: Bearer uspl_your_api_key" ``` ```json Response theme={null} { "message": "Recording thumbnail retrieved successfully", "data": { "recordingId": "rec_abc123", "resource": { "url": "https://s3.amazonaws.com/userplane-recordings/rec_abc123/thumb.jpg?X-Amz-...", "message": "Thumbnail available", "type": "image/jpeg", "size": 34567, "captureEnabled": true } } } ``` ```bash theme={null} curl https://api.userplane.io/api/v1/public/workspace/ws_abc123/recordings/rec_abc123/console-log \ -H "Authorization: Bearer uspl_your_api_key" ``` ```json Response theme={null} { "message": "Recording console log retrieved successfully", "data": { "recordingId": "rec_abc123", "resource": { "url": "https://s3.amazonaws.com/userplane-recordings/rec_abc123/console.json?X-Amz-...", "message": "Console log available", "type": "application/json", "size": 12345, "captureEnabled": true } } } ``` ```bash theme={null} curl https://api.userplane.io/api/v1/public/workspace/ws_abc123/recordings/rec_abc123/network-log \ -H "Authorization: Bearer uspl_your_api_key" ``` ```json Response theme={null} { "message": "Recording network log retrieved successfully", "data": { "recordingId": "rec_abc123", "resource": { "url": "https://s3.amazonaws.com/userplane-recordings/rec_abc123/network.json?X-Amz-...", "message": "Network log available", "type": "application/json", "size": 89012, "captureEnabled": true } } } ``` ```bash theme={null} curl https://api.userplane.io/api/v1/public/workspace/ws_abc123/recordings/rec_abc123/action \ -H "Authorization: Bearer uspl_your_api_key" ``` ```json Response theme={null} { "message": "Recording action replay retrieved successfully", "data": { "recordingId": "rec_abc123", "resource": { "url": "https://s3.amazonaws.com/userplane-recordings/rec_abc123/action.json?X-Amz-...", "message": "Action data available", "type": "application/json", "size": 5678, "captureEnabled": true } } } ``` ## Errors | Error code | Status | Description | | --------------------- | ------ | ---------------------------------- | | `RECORDING_NOT_FOUND` | 404 | Recording does not exist | | `RECORDING_GONE` | 410 | Recording has already been deleted | Also returns [common authentication errors](/api/error-handling#authentication-errors). ## Related Full recording details and metadata Find recordings to download resources from Handle presigned URLs and transient resources Handle 404 and 410 errors for recordings # List Recordings Source: https://docs.userplane.io/api/recordings/list-recordings GET /public/workspace/{workspaceId}/recordings Retrieve a paginated list of recordings with optional filters Returns a paginated list of recordings in the specified workspace with optional filtering and sorting. ## Errors This endpoint returns [common authentication errors](/api/error-handling#authentication-errors). ## Related Full recording details with metadata Download video, thumbnail, and log files Links that generated these recordings Page-based pagination for list endpoints # Search Workspace Source: https://docs.userplane.io/api/search/search-workspace GET /public/workspace/{workspaceId}/search Search across workspace members, projects, domains, and links Find a specific recording link, project, domain, or teammate inside a workspace by name. Use this endpoint to power typeahead pickers, quick-jump UIs, or any flow where a user types a few characters to locate a resource. Pass the resource kind as `type` and the search text as `q`. Each type uses the matching strategy best suited for its data: * `links` — Postgres full-text search with prefix matching, ranked by relevance * `members` — case-insensitive match on member name * `projects` — case-insensitive match on project title * `domains` — case-insensitive match on domain URL The response is a discriminated union keyed on `type`, so `data.type` tells you which shape `data.results` has. ## Errors | Error code | Status | Description | | -------------------------------- | ------ | -------------------------------------------- | | `WORKSPACE_NOT_FOUND` | 404 | Workspace does not exist | | `WORKSPACE_GONE` | 410 | Workspace has been deleted | | `WORKSPACE_SEARCH_TERM_REQUIRED` | 400 | `q` is empty after trimming | | `WORKSPACE_SEARCH_TYPE_INVALID` | 400 | `type` is not one of the four allowed values | | `WORKSPACE_SEARCH_UNAVAILABLE` | 500 | Search backend is temporarily unavailable | Also returns [common authentication errors](/api/error-handling#authentication-errors). ## Related Paginate through all links in a workspace Paginate through all projects in a workspace # Get Current User Source: https://docs.userplane.io/api/user/get-me GET /public/me Retrieve the authenticated user's profile and aggregate stats Returns the profile and aggregate statistics for the user associated with the API key. ## Errors This endpoint only returns [common authentication errors](/api/error-handling#authentication-errors). ## Related Retrieve the workspaces you belong to API key authentication and scoping Make your first API call step by step Handle authentication and authorization errors # Get Invite URL Source: https://docs.userplane.io/api/workspaces/get-invite-url GET /public/workspace/{workspaceId}/invite-url Retrieve the invite URL for a workspace Returns the invite URL for a workspace. Requires the authenticated user to be an active member of the workspace. ## Errors | Error code | Status | Description | | --------------------- | ------ | ------------------------ | | `WORKSPACE_NOT_FOUND` | 404 | Workspace does not exist | Also returns [common authentication errors](/api/error-handling#authentication-errors). ## Related Retrieve workspace details List all workspaces you belong to # Get Workspace Source: https://docs.userplane.io/api/workspaces/get-workspace GET /public/workspace/{workspaceId} Retrieve workspace details by ID Returns details for a specific workspace. Requires the authenticated user to be an active member of the workspace. ## Errors | Error code | Status | Description | | --------------------- | ------ | ------------------------ | | `WORKSPACE_NOT_FOUND` | 404 | Workspace does not exist | Also returns [common authentication errors](/api/error-handling#authentication-errors). ## Related Retrieve the workspace invite URL Projects within this workspace Recording links in this workspace Recordings in this workspace # List Workspaces Source: https://docs.userplane.io/api/workspaces/list-workspaces GET /public/workspaces Retrieve a paginated list of workspaces for the authenticated user Returns a paginated list of workspaces the authenticated user belongs to. ## Errors This endpoint only returns [common authentication errors](/api/error-handling#authentication-errors). ## Related Retrieve details for a specific workspace Your profile and aggregate stats Projects within a workspace Page-based pagination for list endpoints # Downgrading Your Plan Source: https://docs.userplane.io/billing/downgrading Move to a lower plan tier or cancel your subscription You can downgrade your plan or cancel your subscription at any time. Downgrades take effect at the end of your current billing period. ## How downgrades work When you downgrade: 1. Your current plan remains active until the end of the billing period. 2. At the next billing cycle, your workspace moves to the new (lower) plan. 3. Usage limits adjust to the new plan's allowances. 4. On annual plans, any unused rollover resources are recalculated for the new plan. ## Cancelling your subscription Cancelling works the same as downgrading — your plan remains active until the end of the current billing period, then your workspace reverts to the **Free** plan. ## On-hold grace period If a payment fails (e.g. expired credit card), your subscription enters an **on-hold** state for up to **14 days**. During this grace period: * Your workspace continues to function normally. * You can update your payment details to resolve the issue. If payment is not resolved within 14 days, the subscription is cancelled. ## Plan preview Before confirming a downgrade, you can preview the new plan's resource allocations and pricing. This lets you compare what you have now with what the lower plan includes, so there are no surprises. ## Add-ons and credits Seat add-ons are retained when you downgrade. If a billing adjustment results in a credit, it is applied automatically to your next invoice. ## Managing billing To manage your subscription, update payment details, or view invoices: Go to [Workspace Settings > Billing](https://dash.userplane.io/_/settings?tab=plan-usage\&plan-modal=detailed). Click **Manage Subscription** to open the customer portal. ## Related articles * [Subscription Overview](/billing/overview) — plan tiers and pricing. * [Monitoring Your Usage](/billing/monitoring-usage) — track your resource consumption. # Monitoring Your Usage Source: https://docs.userplane.io/billing/monitoring-usage Track your workspace resource consumption and understand usage warnings The usage dashboard in Workspace Settings shows you how much of each resource your workspace has consumed in the current billing period. ## Where to find usage Go to [Workspace Settings > Billing](https://dash.userplane.io/_/settings?tab=plan-usage) to see the usage section. Each resource shows: * A **progress bar** indicating how much has been used. * A **percentage** of the limit consumed. * The **current count** vs the plan limit. ## Usage warning levels As your usage increases, Userplane shows different warning levels: | Level | Meaning | | --------------------- | ------------------------------------------------------------------------------------------------------- | | **Normal** | Usage is well within your plan limits. No action needed. | | **Approaching limit** | You are nearing your plan limit. Consider monitoring usage or planning an upgrade. | | **Limit reached** | The limit has been reached. The resource is blocked until the next billing period or until you upgrade. | ## Notifications If [usage notifications](/account/notifications) are enabled, you receive email alerts when your workspace enters the approaching or reached states. ## What to do when limits are reached 1. **Upgrade your plan** — move to a higher tier with more resources. See [Upgrading Your Plan](/billing/upgrading). 2. **Wait for the next billing period** — period-based resources (recordings, links, screenshots, files) reset at the start of each cycle. On annual plans, unused resources from previous months may have [rolled over](/billing/usage-limits), giving you extra capacity. 3. **Purchase seat add-ons** — if you need more seats without upgrading, see [Seat Add-Ons](/billing/seat-add-ons). 4. **Remove unused members** — if you are at the seat limit, removing inactive members frees up seats. 5. **Request a fair usage increase** — if you are on an "Unlimited" resource and have hit the fair usage limit, [contact support](/support/contact) to request a higher allocation. ## Related articles * [Understanding Usage Limits](/billing/usage-limits) — detailed per-plan breakdowns. * [Subscription Overview](/billing/overview) — plan tiers and pricing. * [Seat Add-Ons](/billing/seat-add-ons) — purchase additional seats. # Subscription Overview Source: https://docs.userplane.io/billing/overview Understand Userplane plans, pricing, and how to check your current subscription Userplane offers five plan tiers to fit teams of different sizes and needs. ## Plan tiers | Plan | Monthly | Annual (per month) | | -------------- | ------- | ------------------ | | **Free** | \$0 | \$0 | | **Essential** | \$59 | \$47 | | **Starter** | \$99 | \$79 | | **Scale Up** | \$249 | \$199 | | **Enterprise** | Custom | Custom | Annual plans are billed yearly at a discounted monthly rate. On annual plans, unused resources from one month roll over to the next within your billing year. See [Understanding Usage Limits](/billing/usage-limits) for details on rollover. New accounts start with a **14-day trial** that includes full access to all features. See [Trial Plans](/billing/trial) for more information. For a full comparison of what is included in each plan, visit the [pricing page](https://userplane.io/pricing). ## What is included Each plan includes a set of resources: * **Seats** — the number of workspace members. Need more? Purchase [seat add-ons](/billing/seat-add-ons). * **Recording links** — how many recording links you can create per billing period. * **Recordings** — how many recordings you can receive per billing period. * **Screenshots** — available on Scale Up and Enterprise plans. * **Files** — available on Scale Up and Enterprise plans. See [Understanding Usage Limits](/billing/usage-limits) for detailed per-plan breakdowns. ## Custom plans If the standard tiers don't fit your needs, we can create a custom plan tailored to your team size and requirements. [Contact support](/support/contact) to discuss options. ## Seat add-ons Need extra seats without upgrading your entire plan? You can purchase additional seats on any paid plan. See [Seat Add-Ons](/billing/seat-add-ons) for details. ## Flexible credits If a billing adjustment results in a credit (for example, from a mid-cycle downgrade or a prorated change), the credit is applied automatically to your next invoice. Credits do not expire within your subscription. ## Checking your current plan 1. Go to [Workspace Settings > Billing](https://dash.userplane.io/_/settings?tab=plan-usage). 2. Your current plan, billing period, and next renewal date are displayed at the top. ## Changing your plan * [Upgrading Your Plan](/billing/upgrading) — move to a higher tier. * [Downgrading Your Plan](/billing/downgrading) — move to a lower tier or cancel. When changing plans, you can preview the new plan's resources and pricing before confirming. Seat add-ons are retained across plan changes, and any flexible credits are applied to the next invoice. # Seat Add-Ons Source: https://docs.userplane.io/billing/seat-add-ons Purchase additional seats beyond your plan allocation Seat add-ons let you add extra workspace members on top of the seats included in your plan, without upgrading to a higher tier. ## What are seat add-ons Each plan includes a base number of seats. If your team grows beyond that number, you can purchase additional seats individually rather than moving to a more expensive plan. ## Who can purchase Only workspace **Owners** and **Admins** can purchase seat add-ons. Members can request additional seats by contacting an owner or admin. ## How to purchase Go to [Workspace Settings > Billing](https://dash.userplane.io/_/settings?tab=plan-usage). Locate the **Seats** add-on section in your billing settings. Click **Add Seats** and choose how many additional seats you need. Confirm and complete the purchase. The extra seats are available immediately. ## Add-ons on plan changes Seat add-ons are retained when you upgrade or downgrade your plan. If you move to a higher plan with more base seats, your add-on seats remain on top of the new base allocation. The same applies when downgrading — your add-on seats carry over. ## Availability Seat add-ons are available on **paid plans only**. They are not available during the trial period or on the Free plan. If you are on a trial, [choose a plan](/billing/trial) first, then purchase add-ons. ## Related articles * [Understanding Usage Limits](/billing/usage-limits) — per-plan resource breakdowns. * [Team Management](/workspace/team-management) — add and manage workspace members. * [Subscription Overview](/billing/overview) — plan tiers and pricing. # Trial Plans Source: https://docs.userplane.io/billing/trial Start with a full-featured 14-day trial — explore everything Userplane offers before committing Every new Userplane workspace starts with a 14-day trial. During the trial you get full access to all features, so you can evaluate the product before choosing a plan. ## What is included The trial gives you the same experience as a paid plan. All features which are available in the paid plan are made available during the trial as well including recordings, recording links, console and network capture, custom metadata, and branding. You cannot upgrade, downgrade, or purchase add-ons during the trial. ## Trial duration The trial lasts **14 days** from the date your workspace is created. A countdown is displayed in Workspace Settings so you always know how much time remains. ## How the trial works When you start a trial, you select a paid plan and enter payment details. Your subscription begins immediately with full access, but **charges are deferred until the trial ends**. This means you get the complete paid experience from day one without being billed upfront. ## What happens when the trial ends At the end of the 14-day trial: * **If you keep the plan** — your deferred charges are billed and the subscription continues as normal. No action needed. * **If you cancel before the trial ends** — you are not charged and your workspace reverts to the **Free** plan. All your recordings, links, and data are preserved — nothing is deleted. ## Cancelling during the trial To cancel before being charged, go to [Workspace Settings > Billing](https://dash.userplane.io/_/settings?tab=workspace) and cancel your subscription. Your workspace reverts to the Free plan at the end of the trial period. ## Related articles * [Subscription Overview](/billing/overview) — plan tiers and pricing. * [Upgrading Your Plan](/billing/upgrading) — how to move to a paid plan. * [Contact Us](/support/contact) — request a trial extension. # Upgrading Your Plan Source: https://docs.userplane.io/billing/upgrading Move to a higher plan tier for more seats, recordings, and features Upgrading your plan gives your workspace more seats, recording links, recordings, and access to additional features. ## Who can upgrade The upgrade flow depends on your role: | Role | What happens | | ---------- | ------------------------------------------------------------------------------------------- | | **Owner** | Can upgrade directly through the checkout flow. | | **Admin** | Can preview plan details and initiate the upgrade flow. | | **Member** | Can request an upgrade. An email is sent to workspace owners notifying them of the request. | ## Upgrading as an owner Go to [Workspace Settings > Billing](https://dash.userplane.io/_/settings?tab=plan-usage\&plan-modal=detailed). Review the available plans and their resource allocations. Click **Upgrade** or select a higher plan. You can choose between monthly and annual billing. If this is your first paid subscription, you are redirected to the checkout page to enter payment details. Complete the checkout. Your workspace is upgraded immediately. ## Previewing as an admin Admins can view plan details and pricing, including a preview of the new plan's resource allocations and cost. From there, they are redirected to the checkout flow alongside the owner approval process. ## Requesting as a member Go to [Workspace Settings > Billing](https://dash.userplane.io/_/settings?tab=plan-usage\&plan-modal=detailed). Click **Request Upgrade**. An email notification is sent to all workspace owners with details of the requested plan. An owner reviews the request and completes the upgrade through the checkout flow. ## Plan changes When you upgrade: * The new plan takes effect immediately. * Existing usage counts carry over to the new plan's limits. * Billing is adjusted for the remaining time in your current billing period. * Seat add-ons are retained on the new plan. * Any flexible credits are applied to the next invoice. * On annual plans, unused rollover resources carry over. Upgrades are not available during the trial period. Choose a plan after your trial ends or when you are ready to commit. See [Trial Plans](/billing/trial) for details. ## Related articles * [Subscription Overview](/billing/overview) — plan tiers and pricing. * [Understanding Usage Limits](/billing/usage-limits) — what each plan includes. * [Trial Plans](/billing/trial) — how the 14-day trial works. # Understanding Usage Limits Source: https://docs.userplane.io/billing/usage-limits Learn how plan resource limits, fair usage, and annual rollover work Each Userplane plan includes a set of resources. Understanding how these limits work helps you choose the right plan and avoid unexpected interruptions. ## Resource types | Resource | Description | Billing | | ------------------- | ---------------------------------------------------------- | ---------------------------------------- | | **Seats** | Number of workspace members. | Lifetime (accumulate over subscription). | | **Recording links** | Number of recording links you can create. | Per billing period (resets each cycle). | | **Recordings** | Number of recordings you can receive. | Per billing period. | | **Screenshots** | Number of screenshots (Scale Up and Enterprise only). | Per billing period. | | **Files** | Number of file attachments (Scale Up and Enterprise only). | Per billing period. | ## How limits work Each plan includes a set of resources. The numbers on the [pricing page](https://userplane.io/pricing) are your plan limits. Resources marked "Unlimited" have a fair usage limit to prevent abuse. If you need more, [contact support](/support/contact) to request a higher allocation. ## Per-plan breakdown ### Free | Resource | Included | | --------------- | -------- | | Seats | 3 | | Recordings | 3 | | Recording links | 3 | ### Essential ($59/mo or $47/mo annual) | Resource | Included | | --------------- | --------- | | Seats | 5 | | Recordings | 25 | | Recording links | Unlimited | ### Starter ($99/mo or $79/mo annual) | Resource | Included | | --------------- | --------- | | Seats | 10 | | Recordings | Unlimited | | Recording links | Unlimited | ### Scale Up ($249/mo or $199/mo annual) | Resource | Included | | --------------- | --------- | | Seats | 15 | | Recordings | Unlimited | | Recording links | Unlimited | | Screenshots | Unlimited | | Files | Unlimited | Enterprise plans have custom allocations for all resources. [Contact support](/support/contact) for details. ## How limits are enforced * **Below limit** — everything works normally. * **At limit** — the resource is blocked until the next billing period, until you upgrade, or until you purchase an add-on. ## Annual plans and rollover Annual subscriptions distribute your yearly allocation across months. Unused resources from one month roll over to the next within your billing year. For example, if your plan includes 300 recordings per year (25 per month) and you use 15 in January, the remaining 10 carry forward to February. ## Fair usage Resources listed as "Unlimited" are subject to a fair usage limit. Most workspaces never approach this limit. If you need a higher allocation, open a [support ticket](/support/contact) and we will review your account. ## Seat add-ons Need more seats than your plan includes? You can purchase additional seats without upgrading. See [Seat Add-Ons](/billing/seat-add-ons) for details. ## Related articles * [Subscription Overview](/billing/overview) — plan tiers and pricing. * [Monitoring Your Usage](/billing/monitoring-usage) — track consumption in real time. * [Seat Add-Ons](/billing/seat-add-ons) — purchase additional seats. # Agent Skills Source: https://docs.userplane.io/developer/agent-skills Install Userplane Agent Skills to give AI agents built-in expertise for working with Userplane Userplane publishes an Agent Skills package that gives AI agents built-in knowledge of Userplane's APIs, SDKs, and workflows. Once installed, agents load the relevant context automatically — no manual prompting required. ## Recommended install paths Plugin install with skills and MCP servers Plugin install with commands, rules, subagents, skills, and MCP servers Plugin install with slash commands, subagents, skills, and MCP servers Use the plugin for your agent when one is available. Use the Skills CLI for other agents that support the Agent Skills standard. ## Skills CLI ```bash theme={null} npx skills add userplanehq/userplane-agent ``` The `skills` CLI is maintained by Vercel Labs. See the [skills.sh documentation](https://skills.sh/docs) for more details. ## Verify ```bash theme={null} npx skills list ``` You should see `userplane-agent` in the output. ## What Agent Skills do Agent Skills are modular capabilities that extend AI agents with domain-specific context. They use a three-level loading system that keeps costs low while making the right knowledge available when needed: | Level | When loaded | What's included | | ---------------- | ---------------------------- | --------------------------------------------- | | **Metadata** | Always (at startup) | Skill name and description — minimal overhead | | **Instructions** | When triggered by your query | Full instructions from the Skill | | **Resources** | As needed | Bundled files, scripts, and references | The agent only loads what's relevant to your current task. ## What's included | Category | Skills | | -------------------------- | ------------------------------------------------------------------------------------- | | **Framework integrations** | React, Next.js, Vue, Angular, Nuxt, Astro, SvelteKit, TanStack Start, and static HTML | | **SDK and reference** | Web SDK, Metadata SDK, CDN installation, sensitive data redaction, and best practices | | **Workflow skills** | `userplane-integrate`, `userplane-audit`, `userplane-debug`, and `userplane-privacy` | ## Which AI agents support skills? Skills work with popular AI coding agents including Claude Code, Cursor, Windsurf, and others. Check each skill's documentation for specific compatibility information. Using Codex, Cursor, or Claude Code? Install the Userplane plugin for that agent instead of the Skills CLI. The plugins include the skills plus the MCP servers and workflow entry points for the host agent. ## Related articles * [Codex Plugin](/integrations/codex) — install skills and MCP servers in Codex. * [Cursor Plugin](/integrations/cursor) — install commands, rules, subagents, skills, and MCP servers in Cursor. * [Claude Code Plugin](/integrations/claude-code) — the full plugin with skills, commands, subagents, and MCP servers. * [Documentation MCP](/integrations/mcp-docs) — connect AI tools to Userplane documentation via MCP. * [Userplane MCP](/integrations/mcp-workspace) — connect AI agents to your workspace for debugging and root cause analysis. * [Installation](/developer/installation) — install the Userplane SDK in your app. # Domain Verification Guide Source: https://docs.userplane.io/developer/domain-verification Technical guide for developers — add the Userplane script to your site and verify domain ownership This guide is for developers who need to add the Userplane script to a website and verify domain ownership. A workspace admin will typically send you these instructions via the "Send to Developer" button in Workspace Settings. ## Overview Userplane verifies domain ownership by checking for its script on your website. The process is: 1. Add the Userplane meta tag and script to your site's HTML. 2. The workspace admin clicks **Verify** in Userplane. 3. Userplane checks your domain for the script and, if found, marks the domain as verified. ## Step 1: Add the script Paste the following into the `` of your site: ```html theme={null} ``` Replace `YOUR_WORKSPACE_ID` with the workspace ID provided by your admin. You can also copy the exact snippet (with the workspace ID already filled in) from **Workspace Settings > Domains** in the Userplane dashboard. The ` ``` Replace `YOUR_WORKSPACE_ID` with your workspace ID. You can copy the exact snippet with your ID pre-filled from **Workspace Settings > Domains** in the Userplane dashboard. ## Script placement The ` ``` Userplane aggressively caches script assets to minimize load time. Only critical code runs upfront; the rest is deferred internally so your page continues to load and render quickly. ## Domains and subdomains You need the Userplane script installed on each domain you want to record on. Recordings have access only to captured events from pages on the same domain that initiated the recording. In most cases, a script installed on the root domain will also capture events from subdomains. For example, a script on `example.com` can see events from pages on `app.example.com`. **Safari exception:** On Safari, events are only captured if the recording page and the script are on the same subdomain. A script on `example.com` will not see events from `app.example.com` in Safari. If Safari support is important to you, install the script on each subdomain separately. ## Handling redirects When a customer opens a recording link, Userplane appends query parameters to the URL — most importantly `userplane-token` and `userplane-action`. These parameters tell the SDK what recording session to start. If your application redirects users from the recording URL — for example, redirecting unauthenticated users to a login page — you need to persist these query parameters through the redirect. If the parameters are lost during the redirect, the recording session cannot start. All `userplane-` prefixed query parameters should be carried through: * `userplane-token` — the recording session token (required) * `userplane-action` — the action type, e.g. `recording` (required) * `userplane-workspace` — the workspace ID * `userplane-meta` — encoded metadata attached to the recording link To avoid keeping this list in sync manually, import `USERPLANE_URL_PARAMS` from the SDK — it exports the canonical array of all parameter names: ```ts theme={null} import { USERPLANE_URL_PARAMS } from '@userplane/sdk'; // Use in a router allowlist, middleware, or analytics filter const ALLOWED_PARAMS = new Set(['search', 'sort', ...USERPLANE_URL_PARAMS]); ``` ### Example: preserving params through a login redirect ```javascript theme={null} // Before redirecting to login, preserve userplane params const url = new URL(window.location.href); const userplaneParams = new URLSearchParams(); for (const [key, value] of url.searchParams) { if (key.startsWith('userplane-')) { userplaneParams.set(key, value); } } // Append to your redirect URL const loginUrl = new URL('/login', window.location.origin); loginUrl.searchParams.set('redirect', `${url.pathname}?${userplaneParams.toString()}`); window.location.href = loginUrl.toString(); ``` ## Content-Security-Policy If your site does not set CSP directives, you can skip this section. If your site specifies `Content-Security-Policy` directives via a header or `` tag, a `frame-src` or `script-src` directive that does not include `*.userplane.io` will block the Userplane script from working correctly. Modify your CSP to allow `*.userplane.io` as both a script source and frame source: ```html theme={null} ``` Or if you set CSP via HTTP headers: ``` Content-Security-Policy: frame-src 'self' *.userplane.io; script-src 'self' *.userplane.io; ``` Without this, the Userplane recorder frame and capture script will be blocked, and recordings will not include console logs or network requests. ## Iframes If the Userplane script is installed inside an iframe, it will only capture events from within that iframe. Top-level page events (console logs, network requests) will not be captured. For full capture coverage, install the script on the top-level page. If your application runs inside an iframe on another site (e.g. embedded widgets or Shopify apps), be aware that top-level logs will not be captured. ## Browser support The Userplane script works in all modern browsers: | Browser | Log capture support | | ------- | ------------------------------------------------------------------------------------ | | Chrome | Fully supported (including Incognito) | | Firefox | Fully supported (including Private Browsing) | | Safari | Supported in standard windows. Private Browsing windows are not currently supported. | | Edge | Fully supported (including InPrivate) | ## Multiple workspaces If you need to associate a single domain with multiple workspaces, you can add multiple meta tags: ```html theme={null} ``` Or pass a comma-separated list: ```html theme={null} ``` ## Verifying installation Open your site in a browser. Open the browser's developer console. Look for Userplane initialization messages confirming the script loaded. Create a test recording link in the Userplane dashboard and open it on your site to confirm the recording flow works end to end. ## Framework guides For framework-specific installation instructions, see the guide for your stack: ## Related articles * [Domain Verification Guide](/developer/domain-verification) — verify domain ownership after installing the script. * [Web SDK](/developer/web-sdk) — programmatic SDK integration for advanced use cases. * [Sensitive Data Redaction](/developer/sensitive-data-redaction) — configure blur to protect sensitive content in recordings. * [Domain Recording Preferences](/recording-links/domain-preferences) — control what data is captured per domain. # Metadata SDK Source: https://docs.userplane.io/developer/metadata-sdk Attach custom metadata to recordings — user info, feature flags, session context, and more The Userplane SDK includes a metadata API that lets you attach custom key-value data to recordings. This data appears in the Info panel when your support team reviews a recording, giving them additional context about the user and their session without having to ask. This page covers **recording metadata** — data attached to recordings via the SDK at capture time. For key-value pairs attached to recording **links**, see [Link Metadata](/recording-links/link-metadata). ## Installation ```bash npm theme={null} npm install @userplane/sdk ``` ```bash yarn theme={null} yarn add @userplane/sdk ``` ```bash pnpm theme={null} pnpm add @userplane/sdk ``` ```bash bun theme={null} bun add @userplane/sdk ``` ## Why attach metadata When your support team opens a recording, they see what happened on screen. But they often need more context: which user is this? What plan are they on? Which feature flags are active? What environment is this? Metadata lets you attach this context automatically, so it's there every time a recording is reviewed — no extra back-and-forth needed. ## API reference ### `set(key, value)` Sets a static metadata key-value pair. Use this for values that are known at initialization and don't change frequently. | Parameter | Type | Description | | --------- | -------------- | ----------------------------------------------------------------------------------------------------- | | `key` | `string` | The metadata key. Appears as a label in the Info Panel. | | `value` | `Serializable` | The value to associate with the key. Can be a string, number, boolean, null, array, or nested object. | Returns: `void` ```javascript theme={null} import { set } from '@userplane/sdk'; set('userId', 'usr_12345'); set('accountId', 'acct_abc123'); set('plan', 'business'); set('environment', 'production'); set('appVersion', '2.4.1'); ``` Static metadata provides defaults. If a dynamic metadata function (see below) returns the same key, the function value takes priority. ### `metadata(fn)` Registers a dynamic metadata function. The function is called when a recording is being submitted, so it captures the most current values at that point in time. | Parameter | Type | Description | | --------- | -------------------------- | ------------------------------------------------------------------------------------------ | | `fn` | `() => SerializableObject` | A function that returns an object of key-value pairs. Called at recording submission time. | Returns: `void` ```javascript theme={null} import { metadata } from '@userplane/sdk'; metadata(() => ({ userId: getCurrentUser().id, plan: getCurrentUser().plan, page: window.location.pathname, featureFlags: getActiveFlags(), })); ``` Calling `metadata()` again overwrites the previous function. Only one metadata function can be registered at a time. ### `clearMetadata(keyOrType?)` Clears metadata. The behavior depends on what you pass: | Parameter | Type | Description | | ----------- | --------------------- | --------------------------------------------------- | | `keyOrType` | `string \| undefined` | What to clear (optional). See behavior table below. | Returns: `void` | Argument | Behavior | | ---------------- | ----------------------------------------------------------------------------- | | *(no argument)* | Clears everything — both the metadata function and all static key-value pairs | | `'function'` | Clears only the registered metadata function | | `'static'` | Clears all static key-value pairs set via `set()` | | Any other string | Clears that specific static key | ```javascript theme={null} import { clearMetadata } from '@userplane/sdk'; // Clear everything clearMetadata(); // Clear only the metadata function clearMetadata('function'); // Clear all static metadata clearMetadata('static'); // Clear a specific key clearMetadata('userId'); ``` ### `getCustomMetadata()` Returns the merged metadata object (static + function), or `null` if no metadata is set. This is primarily used internally by the SDK when the recorder requests metadata, but can be useful for debugging. Returns: `SerializableObject | null` ```javascript theme={null} import { getCustomMetadata } from '@userplane/sdk'; const meta = getCustomMetadata(); console.log(meta); // { userId: 'usr_12345', plan: 'business', page: '/dashboard', ... } ``` When both static metadata and a metadata function are present, the function's return values take priority over static values for the same key. ## Types ### `Serializable` Values passed to `set()` or returned from metadata functions must be serializable: ```typescript theme={null} type Serializable = | string | number | boolean | null | undefined | Serializable[] | { [key: string]: Serializable }; ``` This means you can pass strings, numbers, booleans, nulls, arrays, and nested objects — but not functions, Dates, or class instances. ### `SerializableObject` The return type of a metadata function: ```typescript theme={null} interface SerializableObject { [key: string]: Serializable; } ``` ## How metadata appears Custom metadata is displayed in the **Info Panel** of the recording detail view as a list of key-value pairs, shown below the system metadata section (browser, OS, page URL). Keys appear as labels and values appear as text. ## Common patterns ### Set user context on login ```javascript theme={null} import { set } from '@userplane/sdk'; function onLogin(user) { set('userId', user.id); set('email', user.email); set('accountName', user.account.name); set('plan', user.account.plan); set('role', user.role); } ``` ### Clear metadata on logout ```javascript theme={null} import { clearMetadata } from '@userplane/sdk'; function onLogout() { clearMetadata(); } ``` This prevents user data from leaking into recordings from a subsequent session or a different user. ### Attach feature flags ```javascript theme={null} import { metadata } from '@userplane/sdk'; metadata(() => ({ featureFlags: { newCheckout: isFeatureEnabled('new-checkout'), betaDashboard: isFeatureEnabled('beta-dashboard'), darkMode: isFeatureEnabled('dark-mode'), }, })); ``` Feature flags are useful because they tell your support team exactly which version of a feature the customer was using when they recorded the issue. ### Combine static and dynamic metadata ```javascript theme={null} import { set, metadata } from '@userplane/sdk'; // Static values set once set('appVersion', '2.4.1'); set('environment', 'production'); set('region', 'us-east-1'); // Dynamic values captured at recording time metadata(() => ({ page: window.location.pathname, userId: getCurrentUser()?.id ?? 'anonymous', cartItems: getCartItemCount(), activeExperiments: getActiveExperiments(), })); ``` Static values act as defaults. If the metadata function returns the same key (e.g. `userId`), the function value wins. ### Track page context in a single-page app ```javascript theme={null} import { set } from '@userplane/sdk'; // Update on each route change router.afterEach((to) => { set('currentRoute', to.path); set('routeName', to.name); }); ``` ### Attach error context ```javascript theme={null} import { set } from '@userplane/sdk'; window.addEventListener('error', (event) => { set('lastError', event.message); set('lastErrorSource', `${event.filename}:${event.lineno}`); }); ``` ## URL parameter metadata (userplane-meta) If you cannot use the SDK — for example, in helpdesk macros, server-rendered pages, or third-party tools — you can attach metadata to a recording via a URL query parameter instead. ### Format Append the `userplane-meta` parameter to any recording link URL: ``` ?userplane-meta=key1%3Dval1,key2%3Dval2 ``` The value is a comma-separated list of key-value pairs. Each pair uses `=` as the delimiter between key and name, and the entire value must be URL-encoded (so `=` becomes `%3D`). **Decoded format:** `key1=val1,key2=val2` ### Examples **Helpdesk macro link:** ``` https://record.userplane.io/r/abc123?userplane-meta=ticketId%3D98765,priority%3Dhigh ``` This attaches `ticketId: 98765` and `priority: high` to the recording. **Server-rendered page:** ```html theme={null} Record your screen ``` ### How URL metadata appears Values appear in the Info Panel identically to SDK-set metadata — as key-value pairs in the custom metadata section. There is no visual distinction between URL parameter metadata and SDK metadata. ### Merging with SDK metadata If both URL parameter metadata and SDK metadata are present, the values are merged. When the same key exists in both sources, the SDK value takes priority. ## Tips * Use `set()` for stable values that are known at initialization — user ID, account name, plan, app version, environment. - Use `metadata()` for values that change during the session — current page, active feature flags, cart contents, form state. - Call `clearMetadata()` on logout to avoid leaking user data into subsequent recordings. - Keep metadata keys descriptive. Your support team will see them as-is in the Info Panel. - Avoid attaching large objects. Metadata should be concise context, not a full state dump. - If you cannot install the SDK, use the `userplane-meta` URL parameter to pass metadata through recording link URLs. This is especially useful for helpdesk macros and server-rendered pages. ## Framework guides For framework-specific installation instructions, see the guide for your stack: ## Related articles * [Web SDK](/developer/web-sdk) — initialize the SDK and control recordings programmatically. * [Installation](/developer/installation) — install and configure the embed script. * [Intercom Macros](/integrations/intercom-macros) — use recording links with URL metadata in Intercom macros. * [Zendesk Ticket Sidebar](/integrations/zendesk-ticket-sidebar) — create recording links with ticket context in Zendesk. * [Slack Slash Command](/integrations/slack-commands) — create recording links from Slack with pre-filled references. # Recording Data Reference Source: https://docs.userplane.io/developer/recording-data-reference Technical reference for all data captured in a recording — video, logs, actions, and metadata This reference documents all data types that can be captured during a Userplane recording and their formats. ## Video The screen recording video is the primary data type. **Supported codecs (priority order):** | Codec | Container | Notes | | ----- | --------- | ------------------------------------------------------------------- | | AV1 | WebM, MP4 | Best compression (\~30% better than VP9). Preferred when available. | | VP9 | WebM | Excellent compression, widely supported in modern browsers. | | H.264 | WebM, MP4 | Hardware-accelerated encoding, best Safari compatibility. | | VP8 | WebM | Good compatibility, used as a fallback. | **Limits:** * Maximum file size: **500 MB** * Default maximum duration: **3 minutes** (180 seconds) * Minimum duration: **1 second** ## Thumbnails A thumbnail image is captured for each recording and displayed in the grid view. * **Formats:** PNG, JPEG, WebP * **Maximum size:** 10 MB ## Audio When voice and device audio capture is enabled in [domain recording preferences](/recording-links/domain-preferences), the user's microphone input and system audio are recorded alongside the video. * **Format:** WebM (Opus codec) * **Maximum size:** 50 MB * **Controlled by:** `recordingCaptureAudio` domain preference ## Console logs Browser console output captured during the recording. * **Format:** NDJSON (newline-delimited JSON) * **Maximum size:** 50 MB * **Includes:** Errors, warnings, log messages, and other console output * **Controlled by:** `recordingCaptureClientLog` domain preference ## Network logs HTTP request and response data captured during the recording. * **Format:** JSON * **Includes:** Request URL, method, status code, timing, headers * **Controlled by:** `recordingCaptureNetworkLog` domain preference ## Client actions User interactions captured during the recording. * **Format:** JSON * **Includes:** Clicks (element, coordinates), inputs (field identifiers), page navigations (URL changes) * **Controlled by:** `recordingCaptureClientAction` domain preference ## Backend logs Server-side logs forwarded during the recording session. * **Format:** JSON * **Controlled by:** `recordingCaptureServerLog` domain preference ## System metadata Automatically collected from the customer's browser: | Field | Description | | ---------------- | -------------------------------------------------- | | Browser | Browser name and version | | Operating system | OS name and version | | Page URL | The URL the customer was on when recording started | ## Custom metadata Key-value pairs attached to a recording via the [Metadata SDK](/developer/metadata-sdk) or the `userplane-meta` URL parameter: * Set dynamically with `metadata(callback)` (SDK) * Set statically with `set(key, value)` (SDK) * Passed via URL with `?userplane-meta=key1%3Dval1,key2%3Dval2` * Cleared with `clearMetadata()` (SDK only) When both methods are used, values are merged. SDK values take priority for the same key. Custom metadata appears in the Info Panel alongside system metadata. ## Customer notes Free-text notes submitted by the recording customer. Customers can optionally add a note when they finish recording to describe what they were doing or what they expected to happen. Notes appear in the Info Panel between link info and system metadata. ## Data capture controls What data is captured depends on your [domain recording preferences](/recording-links/domain-preferences): | Preference | Controls | | ------------------------------ | ------------------------------- | | `recordingHideSensitive` | Auto-blur sensitive form fields | | `recordingCaptureClientLog` | Console log capture | | `recordingCaptureClientAction` | Client action capture | | `recordingCaptureNetworkLog` | Network log capture | | `recordingCaptureServerLog` | Backend log capture | | `recordingCaptureAudio` | Voice and device audio capture | These preferences are configured per verified domain in Workspace Settings. # SDK Overview Source: https://docs.userplane.io/developer/sdk-overview Understand the two ways to integrate Userplane — the embed script and the npm SDK — and when to use each Userplane offers two integration paths depending on how much control you need. Most teams start with the embed script and add the npm SDK later if they need programmatic control. ## Embed script (zero-code) The embed script is a single ` ``` This is the recommended starting point. It works with any site and requires no build step. See [Installation](/developer/installation) for setup instructions. ## npm SDK (programmatic) The `@userplane/sdk` package gives you a JavaScript API for full control over the recording lifecycle. Use it when you need to: * **Trigger recordings from your UI** — open the recorder from a button, menu, or support widget instead of relying on link clicks. * **Attach metadata** — send user IDs, account info, feature flags, or session context alongside recordings so your support team has full context. * **Query recording state** — check whether a recording is active, get the session ID, or read connection status to build custom UI. ```javascript theme={null} import { initialize, open } from '@userplane/sdk'; import { setMetadata } from '@userplane/sdk/metadata'; // Initialize once on page load initialize({ workspaceId: 'ws_abc123' }); // Attach context for support setMetadata({ userId: user.id, plan: user.plan, appVersion: __APP_VERSION__, }); // Open the recorder from a support button document.getElementById('help-btn').addEventListener('click', () => { open(); }); ``` ## Choosing the right path | | Embed script | npm SDK | | --------------------------- | ------------------ | ---------------------------- | | **Setup** | One ` ``` You can copy the snippet with your workspace ID pre-filled from **Workspace Settings > Domains** in the Userplane dashboard. ## npm SDK Use the npm SDK when you need programmatic control — triggering recordings from a button, attaching user metadata, or reading recording state. ### Installation ```bash npm theme={null} npm install @userplane/sdk ``` ```bash yarn theme={null} yarn add @userplane/sdk ``` ```bash pnpm theme={null} pnpm add @userplane/sdk ``` ```bash bun theme={null} bun add @userplane/sdk ``` ### Initialization Create an initializer factory and register it with `APP_INITIALIZER` in your app config. Angular calls `APP_INITIALIZER` providers during bootstrap, before the first component renders. ```typescript theme={null} // src/app/initializers/userplane.initializer.ts import { initialize } from '@userplane/sdk'; import { environment } from '../../environments/environment'; export function provideUserplane() { return () => { initialize({ workspaceId: environment.userplaneWorkspaceId }); }; } ``` ```typescript theme={null} // src/app/app.config.ts import { ApplicationConfig, APP_INITIALIZER } from '@angular/core'; import { provideRouter } from '@angular/router'; import { provideUserplane } from './initializers/userplane.initializer'; import { routes } from './app.routes'; export const appConfig: ApplicationConfig = { providers: [ provideRouter(routes), { provide: APP_INITIALIZER, useFactory: provideUserplane, multi: true, }, ], }; ``` Store the workspace ID in your environment file: ```typescript theme={null} // src/environments/environment.ts export const environment = { production: false, userplaneWorkspaceId: 'ws_abc123', }; ``` ```typescript theme={null} // src/environments/environment.prod.ts export const environment = { production: true, userplaneWorkspaceId: 'ws_abc123', }; ``` Angular CLI replaces `environment.ts` with `environment.prod.ts` for production builds automatically. ### URL parameters When a customer opens a recording link, Userplane appends `userplane-token` and `userplane-action` to the URL. If your app uses route guards that redirect unauthenticated users, preserve the `userplane-` prefixed parameters through the redirect: ```typescript theme={null} // src/app/guards/auth.guard.ts import { inject } from '@angular/core'; import { CanActivateFn, Router } from '@angular/router'; import { ActivatedRouteSnapshot } from '@angular/router'; export const authGuard: CanActivateFn = (route: ActivatedRouteSnapshot) => { const router = inject(Router); const authService = inject(AuthService); if (!authService.isAuthenticated()) { const queryParams: Record = {}; for (const [key, value] of Object.entries(route.queryParams)) { if (key.startsWith('userplane-') && typeof value === 'string') { queryParams[key] = value; } } router.navigate(['/login'], { queryParams }); return false; } return true; }; ``` See [Installation](/developer/installation) for the full list of parameters. ## Sensitive data Add `data-userplane-blur` to any element you want blurred in recordings. See [Sensitive Data Redaction](/developer/sensitive-data-redaction) for the full reference. ## Metadata Call `set()` inside the initializer factory after `initialize()`: ```typescript theme={null} // src/app/initializers/userplane.initializer.ts import { initialize, set } from '@userplane/sdk'; import { environment } from '../../environments/environment'; export function provideUserplane() { return () => { initialize({ workspaceId: environment.userplaneWorkspaceId }); set('environment', environment.production ? 'production' : 'development'); }; } ``` See [Metadata SDK](/developer/metadata-sdk) for the full API. ## Example app A complete Angular example is available at [github.com/userplanehq/userplane-sdk-examples/tree/main/examples/angular](https://github.com/userplanehq/userplane-sdk-examples/tree/main/examples/angular). | Variable | Description | | ------------------------ | ----------------------------------------------------- | | `USERPLANE_WORKSPACE_ID` | Your Userplane workspace ID (set in `environment.ts`) | ```bash theme={null} cd examples/angular && npm install && ng serve ``` ## Related articles * [Installation](/developer/installation) — CDN script placement, CSP, and redirect handling. * [Web SDK](/developer/web-sdk) — full SDK API reference. * [Metadata SDK](/developer/metadata-sdk) — attach user context to recordings. * [Sensitive Data Redaction](/developer/sensitive-data-redaction) — blur sensitive content in recordings. # Astro Integration Source: https://docs.userplane.io/frameworks/astro Install Userplane in an Astro application using client-side script blocks This guide covers how to install Userplane in an Astro application. ## Adding the script The fastest way to add Userplane is the CDN embed. Add these two tags to the `` of your base layout: ```astro theme={null} --- // src/layouts/Layout.astro --- ``` You can copy the snippet with your workspace ID pre-filled from **Workspace Settings > Domains** in the Userplane dashboard. ## npm SDK Use the npm SDK when you need programmatic control — triggering recordings from a button, attaching user metadata, or reading recording state. ### Installation ```bash npm theme={null} npm install @userplane/sdk ``` ```bash yarn theme={null} yarn add @userplane/sdk ``` ```bash pnpm theme={null} pnpm add @userplane/sdk ``` ```bash bun theme={null} bun add @userplane/sdk ``` ### Initialization Add a ` ``` Set the variable in your `.env` file: ``` PUBLIC_USERPLANE_WORKSPACE_ID=ws_abc123 ``` `import.meta.env.PUBLIC_*` variables are inlined at build time by Vite. Access them inside ` ``` See [Metadata SDK](/developer/metadata-sdk) for the full API. ## SSR Astro's ` {% block extra_head %}{% endblock %} {% block content %}{% endblock %} ``` Replace `YOUR_WORKSPACE_ID` with your workspace ID. You can copy the exact snippet with your ID pre-filled from **Workspace Settings > Domains** in the Userplane dashboard. Place the tags as early as possible in ``. The Userplane script can only capture console logs and network requests that occur after it initializes — placing it early gives you the most complete recording context. Django's `{% static %}` tag and `STATICFILES_DIRS` are for locally hosted assets. For external CDN URLs, use a plain ` {% endblock %} ``` ## URL parameters When a customer opens a recording link, Userplane appends `userplane-token`, `userplane-action`, and `userplane-workspace` to the URL. If your Django app uses `@login_required` or a middleware that redirects unauthenticated users, carry the `userplane-` prefixed parameters through the redirect: ```python theme={null} # views.py from django.shortcuts import redirect from urllib.parse import urlencode def login_view(request): if request.user.is_authenticated: next_url = request.GET.get("next", "/") # Preserve userplane params params = { k: v for k, v in request.GET.items() if k.startswith("userplane-") } if params: next_url += ("&" if "?" in next_url else "?") + urlencode(params) return redirect(next_url) # ... render login form ``` See [Installation](/developer/installation) for the full list of parameters. ## Sensitive data Add `data-userplane-blur` to any element you want blurred in recordings: ```html theme={null}
``` To blur all inputs across the page, add a meta tag to your base template: ```html theme={null} ``` See [Sensitive Data Redaction](/developer/sensitive-data-redaction) for the full reference. ## Content Security Policy If your Django app sets a `Content-Security-Policy` header (e.g. via `django-csp`), add the Userplane CDN domain to your `script-src` directive: ```python theme={null} # settings.py (django-csp) CSP_SCRIPT_SRC = ("'self'", "https://cdn.userplane.io") ``` See [Installation](/developer/installation) for additional CSP guidance. ## Example app A complete Django example is available at [github.com/userplanehq/userplane-sdk-examples/tree/main/examples/django](https://github.com/userplanehq/userplane-sdk-examples/tree/main/examples/django). | Variable | Description | | ------------------------ | --------------------------- | | `USERPLANE_WORKSPACE_ID` | Your Userplane workspace ID | ```bash theme={null} cd examples/django && pip install -r requirements.txt && python manage.py runserver ``` ## Related articles * [Installation](/developer/installation) — CDN script placement, CSP, iframes, and browser support. * [Domain Verification Guide](/developer/domain-verification) — verify domain ownership after installing the script. * [Sensitive Data Redaction](/developer/sensitive-data-redaction) — blur sensitive content in recordings. * [Web SDK](/developer/web-sdk) — programmatic SDK for apps with a build step. # Next.js Integration Source: https://docs.userplane.io/frameworks/nextjs Install Userplane in a Next.js App Router application using a client provider component This guide covers how to install Userplane in a Next.js (App Router) application. ## Adding the script The fastest way to add Userplane is the CDN embed. Add these two tags to the `` of your root layout: ```html theme={null} ``` You can copy the snippet with your workspace ID pre-filled from **Workspace Settings > Domains** in the Userplane dashboard. ## npm SDK Use the npm SDK when you need programmatic control — triggering recordings from a button, attaching user metadata, or reading recording state. ### Installation ```bash npm theme={null} npm install @userplane/sdk ``` ```bash yarn theme={null} yarn add @userplane/sdk ``` ```bash pnpm theme={null} pnpm add @userplane/sdk ``` ```bash bun theme={null} bun add @userplane/sdk ``` ### Initialization Create a client component that initializes the SDK and mount it in your root layout. ```tsx theme={null} // app/providers/userplane-provider.tsx 'use client'; import { useEffect } from 'react'; export function UserplaneProvider({ children }: { children: React.ReactNode }) { useEffect(() => { import('@userplane/sdk').then(({ initialize }) => { initialize({ workspaceId: process.env.NEXT_PUBLIC_USERPLANE_WORKSPACE_ID!, }); }); }, []); return <>{children}; } ``` ```tsx theme={null} // app/layout.tsx import { UserplaneProvider } from './providers/userplane-provider'; export default function RootLayout({ children }: { children: React.ReactNode }) { return ( {children} ); } ``` The `'use client'` boundary is required because `useEffect` only runs in the browser. The dynamic `import()` is a bundle-size optimization — a static `import {initialize} from '@userplane/sdk'` at the top of the file also works since the SDK is SSR-safe. ### URL parameters Next.js middleware and route guards may redirect users before the SDK reads the `userplane-token` and `userplane-action` query parameters. If your app redirects unauthenticated users to a login page, preserve `userplane-` prefixed parameters through the redirect. Add `USERPLANE_URL_PARAMS` to your middleware allowlist so those params survive the redirect: ```typescript theme={null} // middleware.ts import { NextRequest, NextResponse } from 'next/server'; export function middleware(request: NextRequest) { const { searchParams } = request.nextUrl; const isAuthenticated = checkAuth(request); if (!isAuthenticated) { const loginUrl = new URL('/login', request.url); // Preserve userplane params through the redirect for (const [key, value] of searchParams) { if (key.startsWith('userplane-')) { loginUrl.searchParams.set(key, value); } } return NextResponse.redirect(loginUrl); } return NextResponse.next(); } ``` See [Installation](/developer/installation) for the full list of parameters to preserve. ## Sensitive data Add `data-userplane-blur` to any element you want blurred in recordings. See [Sensitive Data Redaction](/developer/sensitive-data-redaction) for the full reference. ## Metadata Call `set()` after `initialize()` to attach user context to recordings: ```tsx theme={null} 'use client'; import { useEffect } from 'react'; export function UserplaneProvider({ children }: { children: React.ReactNode }) { useEffect(() => { import('@userplane/sdk').then(({ initialize, set }) => { initialize({ workspaceId: process.env.NEXT_PUBLIC_USERPLANE_WORKSPACE_ID! }); set('environment', 'production'); }); }, []); return <>{children}; } ``` See [Metadata SDK](/developer/metadata-sdk) for the full API. ## SSR `@userplane/sdk` is SSR-safe to import — it does not reference `window` or `document` at module evaluation time. A static import at the top of a `'use client'` file will not cause a server-side error. The `initialize()` call must run client-side. Calling it inside `useEffect` (or a dynamic `import()`) guarantees it runs only in the browser. | Concern | Safe? | Notes | | --------------------------------------------- | ----- | ----------------------------- | | Static `import` at top of `'use client'` file | Yes | Module is SSR-safe | | Calling `initialize()` in `useEffect` | Yes | Runs browser-only | | Calling `initialize()` in a Server Component | No | `window` is not available | | Dynamic `import('@userplane/sdk')` | Yes | Bundle-size optimization only | ## Example app A complete Next.js example is available at [github.com/userplanehq/userplane-sdk-examples/tree/main/examples/nextjs](https://github.com/userplanehq/userplane-sdk-examples/tree/main/examples/nextjs). | Variable | Description | | ------------------------------------ | --------------------------- | | `NEXT_PUBLIC_USERPLANE_WORKSPACE_ID` | Your Userplane workspace ID | ```bash theme={null} cd examples/nextjs && npm install && npm run dev ``` ## Related articles * [Installation](/developer/installation) — CDN script placement, CSP, and redirect handling. * [Web SDK](/developer/web-sdk) — full SDK API reference. * [Metadata SDK](/developer/metadata-sdk) — attach user context to recordings. * [Sensitive Data Redaction](/developer/sensitive-data-redaction) — blur sensitive content in recordings. # Nuxt 3 Integration Source: https://docs.userplane.io/frameworks/nuxt Install Userplane in a Nuxt 3 application using a browser-only client plugin This guide covers how to install Userplane in a Nuxt 3 application. ## Adding the script The fastest way to add Userplane is the CDN embed. Add these two tags to the `` in `nuxt.config.ts`: ```typescript theme={null} // nuxt.config.ts export default defineNuxtConfig({ app: { head: { meta: [{ name: 'userplane:workspace', content: 'YOUR_WORKSPACE_ID' }], script: [{ type: 'module', src: 'https://cdn.userplane.io/embed/script.js' }], }, }, }); ``` You can copy the snippet with your workspace ID pre-filled from **Workspace Settings > Domains** in the Userplane dashboard. ## npm SDK Use the npm SDK when you need programmatic control — triggering recordings from a button, attaching user metadata, or reading recording state. ### Installation ```bash npm theme={null} npm install @userplane/sdk ``` ```bash yarn theme={null} yarn add @userplane/sdk ``` ```bash pnpm theme={null} pnpm add @userplane/sdk ``` ```bash bun theme={null} bun add @userplane/sdk ``` ### Initialization Create a plugin file with the `.client.ts` suffix. Nuxt only runs `.client.ts` plugins in the browser, so no additional SSR guard is needed. ```typescript theme={null} // plugins/userplane.client.ts export default defineNuxtPlugin(async () => { const { initialize } = await import('@userplane/sdk'); const config = useRuntimeConfig(); initialize({ workspaceId: config.public.userplaneWorkspaceId, }); }); ``` Expose the workspace ID through Nuxt's runtime config so it is available on both server and client: ```typescript theme={null} // nuxt.config.ts export default defineNuxtConfig({ runtimeConfig: { public: { userplaneWorkspaceId: '', }, }, }); ``` Set the value in your `.env` file: ``` NUXT_PUBLIC_USERPLANE_WORKSPACE_ID=ws_abc123 ``` Nuxt automatically maps `NUXT_PUBLIC_*` environment variables to `runtimeConfig.public.*`. ### URL parameters When a customer opens a recording link, Userplane appends `userplane-token` and `userplane-action` to the URL. If your app uses Nuxt middleware to redirect unauthenticated users, preserve the `userplane-` prefixed parameters through the redirect: ```typescript theme={null} // middleware/auth.ts export default defineNuxtRouteMiddleware((to) => { if (!isAuthenticated()) { const query: Record = {}; for (const [key, value] of Object.entries(to.query)) { if (key.startsWith('userplane-') && typeof value === 'string') { query[key] = value; } } return navigateTo({ path: '/login', query }); } }); ``` See [Installation](/developer/installation) for the full list of parameters. ## Sensitive data Add `data-userplane-blur` to any element you want blurred in recordings. See [Sensitive Data Redaction](/developer/sensitive-data-redaction) for the full reference. ## Metadata Call `set()` inside the plugin after `initialize()`: ```typescript theme={null} // plugins/userplane.client.ts export default defineNuxtPlugin(async () => { const { initialize, set } = await import('@userplane/sdk'); const config = useRuntimeConfig(); initialize({ workspaceId: config.public.userplaneWorkspaceId }); set('environment', 'production'); }); ``` See [Metadata SDK](/developer/metadata-sdk) for the full API. ## SSR The `.client.ts` suffix tells Nuxt to exclude this plugin from the server bundle entirely. You do not need `ssr: false` in `nuxt.config.ts` or any `process.client` guards inside the plugin. `@userplane/sdk` is SSR-safe to import — it does not reference `window` or `document` at module evaluation time. The dynamic `import('@userplane/sdk')` inside the plugin is an optional optimization; a static import also works. | Concern | Safe? | Notes | | ----------------------------------------- | ----- | ---------------------------------- | | `.client.ts` plugin suffix | Yes | Nuxt excludes file from SSR bundle | | Static `import` inside `.client.ts` | Yes | File never runs on server | | Dynamic `import('@userplane/sdk')` | Yes | Bundle-size optimization only | | Calling `initialize()` in a server plugin | No | `window` is not available | ## Example app A complete Nuxt 3 example is available at [github.com/userplanehq/userplane-sdk-examples/tree/main/examples/nuxt](https://github.com/userplanehq/userplane-sdk-examples/tree/main/examples/nuxt). | Variable | Description | | ------------------------------------ | --------------------------- | | `NUXT_PUBLIC_USERPLANE_WORKSPACE_ID` | Your Userplane workspace ID | ```bash theme={null} cd examples/nuxt && npm install && npm run dev ``` ## Related articles * [Installation](/developer/installation) — CDN script placement, CSP, and redirect handling. * [Web SDK](/developer/web-sdk) — full SDK API reference. * [Metadata SDK](/developer/metadata-sdk) — attach user context to recordings. * [Sensitive Data Redaction](/developer/sensitive-data-redaction) — blur sensitive content in recordings. # React Integration Source: https://docs.userplane.io/frameworks/react Install Userplane in a React (Vite) application using a provider component and useEffect This guide covers how to install Userplane in a React application built with Vite. ## Adding the script The fastest way to add Userplane is the CDN embed. Add these two tags to the `` of your `index.html`: ```html theme={null} ``` You can copy the snippet with your workspace ID pre-filled from **Workspace Settings > Domains** in the Userplane dashboard. ## npm SDK Use the npm SDK when you need programmatic control — triggering recordings from a button, attaching user metadata, or reading recording state. ### Installation ```bash npm theme={null} npm install @userplane/sdk ``` ```bash yarn theme={null} yarn add @userplane/sdk ``` ```bash pnpm theme={null} pnpm add @userplane/sdk ``` ```bash bun theme={null} bun add @userplane/sdk ``` ### Initialization Create a `UserplaneProvider` component and wrap your app with it in `main.tsx`. ```tsx theme={null} // src/providers/UserplaneProvider.tsx import { useEffect } from 'react'; import { initialize } from '@userplane/sdk'; export function UserplaneProvider({ children }: { children: React.ReactNode }) { useEffect(() => { initialize({ workspaceId: import.meta.env.VITE_USERPLANE_WORKSPACE_ID, }); }, []); return <>{children}; } ``` ```tsx theme={null} // src/main.tsx import { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; import { UserplaneProvider } from './providers/UserplaneProvider'; import App from './App'; createRoot(document.getElementById('root')!).render( ); ``` Because Vite React apps are pure client-side rendered, you can use a static import at the top of the file — no dynamic `import()` or SSR guards are needed. `useEffect` ensures `initialize()` runs once after mount. ### URL parameters When a customer opens a recording link, Userplane appends `userplane-token` and `userplane-action` to the URL. If your app uses a router that strips unknown search parameters (e.g. a protected route redirect), carry the `userplane-` prefixed params through: ```typescript theme={null} // Example: preserve params through a login redirect const url = new URL(window.location.href); const loginUrl = new URL('/login', window.location.origin); for (const [key, value] of url.searchParams) { if (key.startsWith('userplane-')) { loginUrl.searchParams.set(key, value); } } window.location.href = loginUrl.toString(); ``` See [Installation](/developer/installation) for the full list of parameters. ## Sensitive data Add `data-userplane-blur` to any element you want blurred in recordings. See [Sensitive Data Redaction](/developer/sensitive-data-redaction) for the full reference. ## Metadata Call `set()` after `initialize()` to attach user context to recordings: ```tsx theme={null} import { useEffect } from 'react'; import { initialize, set } from '@userplane/sdk'; export function UserplaneProvider({ children }: { children: React.ReactNode }) { useEffect(() => { initialize({ workspaceId: import.meta.env.VITE_USERPLANE_WORKSPACE_ID }); set('environment', 'production'); }, []); return <>{children}; } ``` See [Metadata SDK](/developer/metadata-sdk) for the full API. ## Example app A complete React example is available at [github.com/userplanehq/userplane-sdk-examples/tree/main/examples/react](https://github.com/userplanehq/userplane-sdk-examples/tree/main/examples/react). | Variable | Description | | ----------------------------- | --------------------------- | | `VITE_USERPLANE_WORKSPACE_ID` | Your Userplane workspace ID | ```bash theme={null} cd examples/react && npm install && npm run dev ``` ## Related articles * [Installation](/developer/installation) — CDN script placement, CSP, and redirect handling. * [Web SDK](/developer/web-sdk) — full SDK API reference. * [Metadata SDK](/developer/metadata-sdk) — attach user context to recordings. * [Sensitive Data Redaction](/developer/sensitive-data-redaction) — blur sensitive content in recordings. # Remix Integration Source: https://docs.userplane.io/frameworks/remix Install Userplane in a Remix application using a root route provider component This guide covers how to install Userplane in a Remix application. ## Adding the script The fastest way to add Userplane is the CDN embed. Remix controls the full HTML document through the root route, so add these two tags to the `` in `app/root.tsx`: ```tsx theme={null} // app/root.tsx import { Links, Meta, Outlet, Scripts, ScrollRestoration } from '@remix-run/react'; export default function App() { return ( <%= csrf_meta_tags %> <%= csp_meta_tag %> <%= stylesheet_link_tag "application" %> <%= yield(:head) %> <%= yield %> ``` Replace `YOUR_WORKSPACE_ID` with your workspace ID. You can copy the exact snippet with your ID pre-filled from **Workspace Settings > Domains** in the Userplane dashboard. Place the tags as early as possible in ``. The Userplane script can only capture console logs and network requests that occur after it initializes — placing it early gives you the most complete recording context. Rails' `javascript_include_tag` helper accepts full URLs (`<%= javascript_include_tag "https://cdn.userplane.io/embed/script.js" %>`), but a plain ` <% end %> ``` ## Turbo compatibility Rails 7+ includes **Turbo** by default, which performs partial page replacements instead of full reloads. The Userplane CDN script placed in `` loads on the initial page visit and persists across Turbo navigations — no special configuration is needed. If you initialize custom JavaScript alongside Userplane, listen for `turbo:load` rather than `DOMContentLoaded`: ```javascript theme={null} document.addEventListener('turbo:load', () => { // Code that should run on every page navigation }); ``` ## URL parameters When a customer opens a recording link, Userplane appends `userplane-token` and `userplane-action` to the URL. If your Rails app redirects unauthenticated users (e.g. via Devise), carry the `userplane-` prefixed parameters through the redirect: ```ruby theme={null} # app/controllers/application_controller.rb class ApplicationController < ActionController::Base before_action :authenticate_user! private def after_sign_in_path_for(resource) stored_location = stored_location_for(resource) || root_path userplane_params = request.query_parameters.select { |k, _| k.start_with?("userplane-") } if userplane_params.any? uri = URI.parse(stored_location) existing = URI.decode_www_form(uri.query || "") uri.query = URI.encode_www_form(existing + userplane_params.to_a) uri.to_s else stored_location end end end ``` See [Installation](/developer/installation) for the full list of parameters. ## Sensitive data Add `data-userplane-blur` to any element you want blurred in recordings. See [Sensitive Data Redaction](/developer/sensitive-data-redaction) for the full reference. ## Content Security Policy If your Rails app uses the built-in CSP DSL, add the Userplane CDN domain: ```ruby theme={null} # config/initializers/content_security_policy.rb Rails.application.configure do config.content_security_policy do |policy| policy.script_src :self, "https://cdn.userplane.io" end end ``` See [Installation](/developer/installation) for additional CSP guidance. ## Example app A complete Ruby on Rails example is available at [github.com/userplanehq/userplane-sdk-examples/tree/main/examples/rails](https://github.com/userplanehq/userplane-sdk-examples/tree/main/examples/rails). | Variable | Description | | ------------------------ | --------------------------- | | `USERPLANE_WORKSPACE_ID` | Your Userplane workspace ID | ```bash theme={null} cd examples/rails && bundle install && bin/rails server ``` ## Related articles * [Installation](/developer/installation) — CDN script placement, CSP, iframes, and browser support. * [Domain Verification Guide](/developer/domain-verification) — verify domain ownership after installing the script. * [Sensitive Data Redaction](/developer/sensitive-data-redaction) — blur sensitive content in recordings. * [Web SDK](/developer/web-sdk) — programmatic SDK for apps with a build step. # SolidJS Integration Source: https://docs.userplane.io/frameworks/solidjs Install Userplane in a SolidJS application using the CDN embed or programmatic script injection This guide covers how to install Userplane in a SolidJS application built with Vite. ## Adding the script The fastest way to add Userplane is the CDN embed. Add these two tags to the `` of your `index.html` at the project root: ```html theme={null} ``` Replace `YOUR_WORKSPACE_ID` with your workspace ID. You can copy the exact snippet with your ID pre-filled from **Workspace Settings > Domains** in the Userplane dashboard. Vite serves `index.html` as the entry point for SolidJS SPAs, so any tags placed in `` load before the application mounts. Place the tags as early as possible — the Userplane script can only capture console logs and network requests that occur after it initializes. ## npm SDK Use the npm SDK when you need programmatic control — triggering recordings from a button, attaching user metadata, or reading recording state. ### Installation ```bash npm theme={null} npm install @userplane/sdk ``` ```bash yarn theme={null} yarn add @userplane/sdk ``` ```bash pnpm theme={null} pnpm add @userplane/sdk ``` ```bash bun theme={null} bun add @userplane/sdk ``` ### Initialization Create a `UserplaneProvider` component and wrap your app with it in `index.tsx`. ```tsx theme={null} // src/providers/UserplaneProvider.tsx import { onMount, ParentComponent } from 'solid-js'; import { initialize } from '@userplane/sdk'; const UserplaneProvider: ParentComponent = (props) => { onMount(() => { initialize({ workspaceId: import.meta.env.VITE_USERPLANE_WORKSPACE_ID, }); }); return <>{props.children}; }; export default UserplaneProvider; ``` ```tsx theme={null} // src/index.tsx import { render } from 'solid-js/web'; import UserplaneProvider from './providers/UserplaneProvider'; import App from './App'; render( () => ( ), document.getElementById('root')! ); ``` SolidJS's `onMount` runs once after the component mounts in the browser, making it the equivalent of React's `useEffect(() => {}, [])`. Because Vite SolidJS apps are client-side rendered, a static import at the top of the file works — no dynamic `import()` or SSR guards are needed. ## SolidStart (SSR) If you are using **SolidStart** (the SSR meta-framework), there is no raw `index.html`. Use a dynamic `import()` inside `onMount` to ensure the SDK only initializes in the browser: ```tsx theme={null} // src/providers/UserplaneProvider.tsx import { onMount, ParentComponent } from 'solid-js'; const UserplaneProvider: ParentComponent = (props) => { onMount(async () => { const { initialize } = await import('@userplane/sdk'); initialize({ workspaceId: import.meta.env.VITE_USERPLANE_WORKSPACE_ID, }); }); return <>{props.children}; }; export default UserplaneProvider; ``` Mount the provider in your root layout at `src/app.tsx`. | Concern | Safe? | Notes | | ----------------------------------------------- | ----- | ------------------------- | | Static `import` in a client-only SPA | Yes | No SSR involved | | Dynamic `import()` in `onMount` with SolidStart | Yes | Runs browser-only | | Calling `initialize()` during server render | No | `window` is not available | SolidJS does not yet provide a built-in ` ``` Replace `YOUR_WORKSPACE_ID` with your workspace ID. You can copy the exact snippet with your ID pre-filled from **Workspace Settings > Domains** in the Userplane dashboard. Place the tags as early as possible in ``. The Userplane script can only capture console logs and network requests that occur after it initializes — placing it early gives you the most complete recording context. ## Sensitive data To blur an element in recordings, add the `data-userplane-blur` attribute: ```html theme={null}
``` To blur all inputs across the page, add a meta tag: ```html theme={null} ``` Supported `content` values for the meta tag: | Value | What is blurred | | -------- | ---------------------------------------------------- | | `inputs` | All ``, `