Recordings
Capture user workflows from a browser (or any client you build), upload the events + screenshots, and let SOPFlow's AI pipeline draft SOP steps. Powers the SOPFlow Chrome extension; same surface is open to your own clients.
tRPC, not REST
The recording surface lives on POST /trpc/recording.<procedure> rather than /api/v1/*. Same Bearer-key auth, just a different base path. The screenshot upload uses regular multipart at POST /api/v1/recordings/:id/screenshot/:seq.
Lifecycle
- create — get a recording id and the session starts.
- uploadEvents — POST batches as the user interacts (max 100 per call).
- screenshot — multipart POST for any sequence number.
- finalize — server flips status to
processingand runs the AI pipeline. - getStatus — poll until
ready(orfailed); generated steps are ready. - importToSOP — convert selected drafts into
sop_steps.
POST/trpc/recording.create
Open a new recording session. Returns the recordingId you'll use for every subsequent call.
Body fields
| Name | Type | Required | Description |
|---|---|---|---|
| source | string | required | chrome_extension | desktop_app — for analytics & display only. |
Example response
{
"result": {
"data": {
"recordingId": "31ab37ce-dce6-4590-a640-b175a1454055"
}
}
}POST/trpc/recording.uploadEvents
Append an ordered batch of events. sequenceNumber must strictly increase across the recording (and within each batch). Max 100 events per call.
Body fields
| Name | Type | Required | Description |
|---|---|---|---|
| recordingId | uuid | required | — |
| events | Event[] | required | 1–100 events. |
Event
| Name | Type | Required | Description |
|---|---|---|---|
| sequenceNumber | integer | required | Strictly increasing per recording. |
| eventType | string | required | click | input | select | navigation | keyboard_shortcut | scroll | form_submit | context_menu | double_click | drag_drop | tab_switch | app_switch | file_upload |
| url | string | optional | — |
| pageTitle | string | optional | — |
| target | object | required | Element metadata: cssSelector, xpath, textContent, ariaLabel, elementType, etc. |
| eventData | object | optional | Type-specific payload (input value, key combo, scroll delta…). |
| screenshotKey | string | optional | Set if you uploaded a screenshot for this seq via the multipart endpoint. |
| timestamp | integer | required | Unix ms. |
Example response
{ "result": { "data": { "inserted": 5 } } }POST/api/v1/recordings/:recordingId/screenshot/:seq
Multipart upload of a single image/webp ≤ 5 MB for one event. The screenshot is linked to the matching event row when (or if) it arrives.
Multipart fields
| Name | Type | Required | Description |
|---|---|---|---|
| file | image/webp | required | Max 5 MB, webp only. |
Example response
{ "objectKey": "recording_screenshots/<org>/<rec>/0.webp", "sizeBytes": 12480 }POST/trpc/recording.finalize
Stop accepting events and kick off AI processing. Returns a job id. API-key clients must poll recording.getStatus; the session-only SSE endpoint does not expose API-key-owned jobs.
Body fields
| Name | Type | Required | Description |
|---|---|---|---|
| recordingId | uuid | required | — |
| durationMs | integer | optional | Total recording duration in milliseconds. |
Example response
{ "result": { "data": { "status": "processing", "jobId": "..." } } }GET/trpc/recording.getStatus
Poll for processing completion. Pass input via query string (URL-encoded JSON).
Query parameter
| Name | Type | Required | Description |
|---|---|---|---|
| input | JSON | required | URL-encoded {"recordingId":"<uuid>"} |
Example response
{
"result": {
"data": {
"recording": {
"id": "31ab37ce-...",
"status": "ready",
"eventCount": 42,
"screenshotCount": 18,
"durationMs": 65234
},
"generatedSteps": [
{
"id": "...",
"stepNumber": 1,
"title": "Navigate to the Login page",
"description": "Open the SOPFlow login URL.",
"screenshotKey": "recording_screenshots/<org>/<rec>/0.webp",
"sourceEventIds": ["..."]
}
]
}
}
}POST/trpc/recording.importToSOP
Append selected generated steps to a kanban phase as real sop_steps. The recording is auto-archived on success.
Body fields
| Name | Type | Required | Description |
|---|---|---|---|
| recordingId | uuid | required | — |
| sopVersionId | uuid | required | The version to append into (server validates ownership). |
| phaseId | uuid | required | — |
| steps | Step[] | required | 1–50 generated steps to import. |
Step
| Name | Type | Required | Description |
|---|---|---|---|
| generatedStepId | uuid | required | — |
| title | string | required | Editable — server uses what you send, not the original. |
| description | string | null | optional | — |
Example response
{ "result": { "data": { "createdSteps": [{ "id": "...", "title": "..." }] } } }Provisioning a key for your client
For server-to-server use, mint an API key in Settings → API keys with the read + write scopes and keep it on your trusted server. Never embed a shared service key in public browser code.
For browser extensions and desktop clients that act on behalf of an end-user, use the post-OAuth bridge:
POST/trpc/apiKeys.createForExtension
Mint a fresh sk_live_… key in the calling user's workspace, named 'Chrome extension (<browser>)'. Editor-procedure (eligible signed-in editors and admins can connect their browser); skips the api_access plan gate. The web app's /extension/connect page calls this after the user signs in, then redirects back to your client with the key in the URL fragment so it never lands in a server access log.
Body fields
| Name | Type | Required | Description |
|---|---|---|---|
| clientName | string | required | Display label — shown in Settings → API keys (e.g. 'Chrome', 'My Recorder'). |
Example response
{ "result": { "data": { "key": "sk_live_…", "prefix": "sk_live_abcd" } } }End-to-end example
# 1. Create a new recording session
curl -X POST https://sopflow.io/trpc/recording.create \
-H "Authorization: Bearer sk_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"source":"chrome_extension"}'
# 2. Stream events as you capture them (max 100 per call)
curl -X POST https://sopflow.io/trpc/recording.uploadEvents \
-H "Authorization: Bearer sk_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"recordingId":"<from step 1>",
"events":[
{"sequenceNumber":0,"eventType":"click","url":"https://example.com",
"pageTitle":"Home","target":{"cssSelector":"button.cta",
"elementType":"BUTTON","textContent":"Sign in"},"timestamp":1730000000000}
]
}'
# 3. Optionally POST a screenshot for any sequenceNumber
curl -X POST \
https://sopflow.io/api/v1/recordings/<id>/screenshot/0 \
-H "Authorization: Bearer sk_live_YOUR_KEY" \
-F "file=@frame.webp;type=image/webp"
# 4. Finalize — kicks off the AI pipeline asynchronously
curl -X POST https://sopflow.io/trpc/recording.finalize \
-H "Authorization: Bearer sk_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"recordingId":"<id>","durationMs":65000}'
# 5. Poll for the result (status flips to "ready")
curl "https://sopflow.io/trpc/recording.getStatus?input=%7B%22recordingId%22%3A%22<id>%22%7D" \
-H "Authorization: Bearer sk_live_YOUR_KEY"Notes
- Privacy is the client's responsibility. The server stores whatever
eventData.valueyou send. The Chrome extension masks password fields and credit-card autocomplete fields before they leave the page; if you build your own client, do the same. The pipeline does a regex sweep for cards / SSNs / emails as a last-line defense, but don't rely on it. - Rate limits. tRPC requests are subject to the general API limit (300 requests per minute per IP), not the REST per-key budgets. Batch up to 100 events per call and handle 429 responses.
- Restart recovery. If the API restarts mid-processing, the recording is auto-marked
failedon the next boot. Callrecording.reprocessto retry.