# 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.
## 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:
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:
## 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) |
## Key actions
Click the three-dot menu next to any key to access management actions:
| 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**.
A confirmation dialog appears. Type **REVOKE** to confirm, then click **Revoke Key**.
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**.
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
Click **Rotate Key** to proceed.
The old key is immediately invalidated. Your new key is displayed in the 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 `