Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/brimblehq/rexec/llms.txt

Use this file to discover all available pages before exploring further.

The Recordings API enables capturing terminal sessions in asciicast format for playback and sharing.

Recording Architecture

Storage Options

Recordings support three storage backends (priority order):
  1. Cloudflare R2 (Recommended): Global CDN with edge caching
  2. S3-Compatible: Any S3-compatible object storage
  3. PostgreSQL: Database storage (default fallback)

Configuration

R2_ACCOUNT_ID=your-account-id
R2_ACCESS_KEY_ID=your-access-key
R2_SECRET_ACCESS_KEY=your-secret
R2_BUCKET=rexec-recordings
R2_PUBLIC_URL=https://recordings.yourdomain.com

Format: Asciicast v2

Recordings use the asciicast v2 format:
{"version": 2, "width": 120, "height": 30, "timestamp": 1642248000, "duration": 45.2, "title": "Demo"}
[0.123, "o", "$ "]
[1.456, "o", "ls -la\r\n"]
[1.789, "o", "total 24\r\ndrwxr-xr-x..."]
Each line after header: [time_in_seconds, event_type, data]

Start Recording

Begin recording a terminal session.
curl -X POST https://api.rexec.sh/api/recordings/start \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "container_id": "abc123",
    "title": "Production Deploy"
  }'
POST /api/recordings/start

Request

container_id
string
required
Container ID to record (Docker ID or name)
title
string
Recording title (default: “Recording YYYY-MM-DD HH:MM”)

Response

recording_id
string
UUID of the recording
started_at
string
ISO 8601 timestamp
container_id
string
Resolved Docker container ID
message
string
Confirmation message
Response Example
{
  "recording_id": "550e8400-e29b-41d4-a716-446655440000",
  "started_at": "2024-01-15T14:30:00Z",
  "container_id": "abc123def456",
  "message": "Recording started"
}

Error Responses

409 Conflict
{
  "error": "already recording this terminal"
}

Stop Recording

Stop recording and save the session.
curl -X POST https://api.rexec.sh/api/recordings/abc123/stop \
  -H "Authorization: Bearer $TOKEN"
POST /api/recordings/:containerId/stop

Path Parameters

containerId
string
required
Container ID (Docker ID or name)

Response

recording_id
string
UUID of the saved recording
duration_ms
number
Recording duration in milliseconds
duration
string
Human-readable duration (e.g., “2m 35s”)
events_count
number
Total number of recorded events
size_bytes
number
Recording size in bytes
share_token
string
Public share token (22 characters)
storage_type
string
Where recording is stored: “r2”, “s3”, or “database”
cdn_url
string
Public CDN URL (R2/S3 only)
Response Example
{
  "recording_id": "550e8400-e29b-41d4-a716-446655440000",
  "duration_ms": 155000,
  "duration": "2m 35s",
  "events_count": 342,
  "size_bytes": 45312,
  "share_token": "xK9mP2nVqL8sR4tYwZ",
  "message": "Recording saved",
  "storage_type": "r2",
  "cdn_url": "https://recordings.yourdomain.com/550e8400-e29b-41d4-a716-446655440000.cast"
}

Error Responses

404 Not Found
{
  "error": "no active recording for this terminal"
}
403 Forbidden
{
  "error": "not authorized"
}

Get Recording Status

Check if a container is currently being recorded. GET /api/recordings/:containerId/status

Response

{
  "recording": true,
  "recording_id": "550e8400-e29b-41d4-a716-446655440000",
  "started_at": "2024-01-15T14:30:00Z",
  "duration_ms": 65000,
  "events_count": 142
}

List Recordings

Retrieve all recordings for the authenticated user.
curl https://api.rexec.sh/api/recordings \
  -H "Authorization: Bearer $TOKEN"
GET /api/recordings

Response

recordings
array
Array of recording objects
Response Example
{
  "recordings": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "title": "Production Deploy",
      "duration_ms": 155000,
      "duration": "2m 35s",
      "size_bytes": 45312,
      "is_public": false,
      "share_token": "xK9mP2nVqL8sR4tYwZ",
      "share_url": "/r/xK9mP2nVqL8sR4tYwZ",
      "created_at": "2024-01-15T14:30:00Z",
      "storage_type": "r2",
      "cdn_url": "https://recordings.yourdomain.com/550e8400.cast"
    }
  ]
}

Get Recording

Retrieve metadata for a specific recording. GET /api/recordings/:id

Path Parameters

id
string
required
Recording UUID

Authorization

  • Requires authentication if recording is private
  • Public recordings accessible without auth

Response

Same structure as single item in List Recordings.

Get Recording by Token

Retrieve recording metadata using public share token. GET /api/recordings/shared/:token

Path Parameters

token
string
required
Share token (22 characters)

Response

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "title": "Production Deploy",
  "duration_ms": 155000,
  "duration": "2m 35s",
  "created_at": "2024-01-15T14:30:00Z",
  "cdn_url": "https://recordings.yourdomain.com/550e8400.cast"
}

Stream Recording

Download the recording file (asciicast format).
curl https://api.rexec.sh/api/recordings/550e8400/stream \
  -H "Authorization: Bearer $TOKEN" \
  -o recording.cast
GET /api/recordings/:id/stream

Response

  • Content-Type: application/x-asciicast
  • Content-Disposition: attachment; filename="{title}.cast"
  • Cache-Control: public, max-age=31536000, immutable

Behavior by Storage Type

Redirects to global CDN URL:
302 Found
Location: https://recordings.yourdomain.com/550e8400.cast

Stream Recording by Token

Public access to recording file using share token. GET /api/recordings/shared/:token/stream

Path Parameters

token
string
required
Share token

Response

Same as Stream Recording (no authentication required).

Update Recording

Update recording settings (title, visibility).
curl -X PATCH https://api.rexec.sh/api/recordings/550e8400 \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "is_public": true,
    "title": "Updated Title"
  }'
PATCH /api/recordings/:id

Request

is_public
boolean
Make recording publicly accessible
title
string
Update recording title

Response

{
  "message": "recording updated"
}

Delete Recording

Permanently delete a recording.
curl -X DELETE https://api.rexec.sh/api/recordings/550e8400 \
  -H "Authorization: Bearer $TOKEN"
DELETE /api/recordings/:id

Behavior

  1. Deletes from external storage (R2/S3) if applicable
  2. Removes database record
  3. Invalidates share token

Response

{
  "message": "recording deleted"
}

Recording Event Capture

The recording system captures three event types:

Output Events

recordingHandler.AddEvent(containerID, "o", outputData, 0, 0)
  • Type: "o"
  • Data: Terminal output bytes (base64 encoded in JSON)
  • When: Every terminal output chunk

Input Events (Optional)

recordingHandler.AddEvent(containerID, "i", inputData, 0, 0)
  • Type: "i"
  • Data: User input
  • When: User types or pastes

Resize Events

recordingHandler.AddEvent(containerID, "r", "", cols, rows)
  • Type: "r"
  • Data: Empty string
  • Cols/Rows: New terminal dimensions
  • When: Terminal window resized

Event Structure

type RecordingEvent struct {
    Time int64  `json:"t"`           // Milliseconds since start
    Type string `json:"e"`           // "o", "i", or "r"
    Data string `json:"d"`           // Event data
    Cols int    `json:"c,omitempty"` // For resize
    Rows int    `json:"r,omitempty"` // For resize
}

Playback Integration

Asciinema Player

<!DOCTYPE html>
<html>
<head>
  <link rel="stylesheet" type="text/css" href="/asciinema-player.css" />
</head>
<body>
  <asciinema-player 
    src="https://api.rexec.sh/api/recordings/550e8400/stream"
    cols="120" 
    rows="30"
    autoplay
    loop
  ></asciinema-player>
  <script src="/asciinema-player.min.js"></script>
</body>
</html>

Best Practices

Performance

  • Use R2/S3 storage for production (offloads server bandwidth)
  • Enable CDN for global playback performance
  • Set appropriate cache headers (1 year for immutable recordings)

Storage Management

  • Implement retention policies to delete old recordings
  • Monitor storage costs (database vs. object storage)
  • Consider compression for long recordings

Security

  • Share tokens are cryptographically random (16 bytes base64)
  • Public recordings accessible without authentication
  • Private recordings require ownership verification
  • R2/S3 URLs are public if configured with public bucket

Recording Quality

  • Typical Sizes: 10-50 KB per minute of terminal activity
  • Event Count: 100-500 events per minute (varies by activity)
  • Compression: Asciicast format is already efficient (line-delimited JSON)

Complete Recording Workflow

1

Start Recording

const { recording_id } = await fetch('/api/recordings/start', {
  method: 'POST',
  body: JSON.stringify({ container_id: 'abc123', title: 'Demo' })
}).then(r => r.json());
2

User Interacts

Terminal I/O automatically captured by handler:
  • Output events: AddEvent(containerID, "o", data, 0, 0)
  • Resize events: AddEvent(containerID, "r", "", cols, rows)
3

Stop Recording

const { share_token, cdn_url } = await fetch(
  `/api/recordings/${containerID}/stop`,
  { method: 'POST' }
).then(r => r.json());

console.log(`Share at: /r/${share_token}`);
4

Playback

<asciinema-player src="${cdn_url}" />

Troubleshooting

No Events Recorded

Check container ID resolution:
// Recording handler resolves names to Docker IDs
dockerID := h.resolveContainerID(containerID)
// Events must use same resolved ID
h.AddEvent(dockerID, "o", data, 0, 0)

Storage Upload Failures

Verify environment variables:
R2_ACCOUNT_ID=abc123
R2_ACCESS_KEY_ID=key
R2_SECRET_ACCESS_KEY=secret
R2_BUCKET=recordings
R2_PUBLIC_URL=https://recordings.example.com
Test connection:
r2Store, err := storage.NewR2StoreFromEnv()
if err != nil {
    log.Fatal("R2 init failed:", err)
}
Required IAM permissions:
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:PutObject",
        "s3:GetObject",
        "s3:DeleteObject"
      ],
      "Resource": "arn:aws:s3:::bucket-name/*"
    }
  ]
}

Build docs developers (and LLMs) love