Reading recordings
These endpoints read and change recordings in the token owner’s workspace. Every recording endpoint takes either the numeric id or the share_id from a share link as :id. A recording outside the workspace, or in the trash, gets 404.
List recordings
Section titled “List recordings”GET /recordings lists the workspace’s ready recordings, newest first, 50 a page.
| Parameter | Notes |
|---|---|
q | Matches the title, description or page URL, case-insensitive |
kind | video, replay or screenshot |
folder | A folder id from GET /folders |
tag | One tag |
author | A member’s numeric user id |
problems | true for recordings with at least one error or failed request |
status | Triage status: open, in_progress, resolved or wont_fix |
priority | urgent, high, medium or low |
assignee | The assigned member’s numeric user id |
since | An ISO 8601 time. Only recordings created at or after it |
page | Page number, from 1 |
Unknown values for kind, status and priority are ignored rather than refused.
curl -G https://app.faylee.app/api/v1/recordings \ -H "Authorization: Bearer $FAYLEE_TOKEN" \ --data-urlencode "problems=true" \ --data-urlencode "since=2026-09-29T00:00:00Z"{ "recordings": [ { "id": 4812, "share_id": "V1StGXR8_Z", "kind": "video", "title": "Checkout fails with a 500", "status": "ready", "error_count": 2, "failed_request_count": 1, "share_url": "https://app.faylee.app/s/V1StGXR8_Z", "author": "Ann Park" } ], "page": 1, "total_pages": 3}Each item is a full recording object. The example above is shortened. A since that is not an ISO 8601 time gets 422 with since must be an ISO 8601 time.
To poll for new recordings, pass the created_at of the newest one you have seen as since. To be told instead, subscribe to the recording.ready webhook.
Get a recording
Section titled “Get a recording”GET /recordings/:id returns the recording object. Unlike the list, it also returns recordings that are still uploading or processing, or that failed.
curl https://app.faylee.app/api/v1/recordings/V1StGXR8_Z \ -H "Authorization: Bearer $FAYLEE_TOKEN"Update a recording
Section titled “Update a recording”PATCH /recordings/:id changes the title, description or who can open the recording, and returns the recording object.
| Field | Notes |
|---|---|
recording.title | |
recording.description | |
recording.visibility | workspace or public. See below for password |
curl -X PATCH https://app.faylee.app/api/v1/recordings/V1StGXR8_Z \ -H "Authorization: Bearer $FAYLEE_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "recording": { "visibility": "public" } }'The API cannot set a share password. Setting password works only on a recording that already has a password from the app. Otherwise it gets 422 with Share password is needed for a password-protected link. A change of visibility is written to the workspace’s audit log, as it is in the app.
Trash a recording
Section titled “Trash a recording”DELETE /recordings/:id moves the recording to the trash and returns 204. Only the recording’s author or a workspace admin can do this. Anyone else gets 403.
The recording is deleted for good after 30 days, unless someone restores it from the trash in the app.
Capture data
Section titled “Capture data”GET /recordings/:id/capture returns the recording’s capture bundle, data.json, as it was uploaded. Its format is described in Capture format.
curl https://app.faylee.app/api/v1/recordings/V1StGXR8_Z/capture \ -H "Authorization: Bearer $FAYLEE_TOKEN" -o data.jsonEvidence text
Section titled “Evidence text”With format=evidence, the same endpoint returns text/plain: the capture written out for a language model to read. It starts with the kind, duration, title and description, the environment and any metadata the app attached. Then comes a timeline with one line per event, each starting with its time in milliseconds since the recording started:
curl "https://app.faylee.app/api/v1/recordings/V1StGXR8_Z/capture?format=evidence" \ -H "Authorization: Bearer $FAYLEE_TOKEN"[t=4900ms] user clicked button "Pay now"The timeline covers pages, actions, console messages, errors, requests, socket messages, page edits, the reporter’s marks and Web Vitals. Long values are shortened. When the text would pass 200,000 characters, the least telling events (plain logs, successful requests, measurements) are left out first, and a last line says how many. Stack frames that Faylee resolved through the site’s source maps point at the original source.
Both forms return 422 when the recording’s storage is no longer connected. The evidence form also returns 422 for a capture made with an older format version, or a damaged one.
Frames
Section titled “Frames”GET /recordings/:id/frames returns still images from a video or a replay.
| Parameter | Notes |
|---|---|
mode | overview (default), at or range |
at | For mode=at: comma-separated times in milliseconds, up to 20 |
from, to | For mode=range: the window in milliseconds. Defaults to the whole recording |
count | For mode=range: how many frames to spread across the window, 1 to 20, 5 by default |
size | small, medium or large: 360, 720 or 1080 pixels high. medium by default |
overview returns one image of 6 to 16 evenly spaced frames in a grid, each labelled with its time. Longer recordings get more frames.
{ "mode": "overview", "size": "medium", "columns": 4, "rows": 2, "t_ms": [3000, 9000, 15000, 21000, 27000, 33000, 39000, 45000], "url": "https://storage.example.com/recordings/V1StGXR8_Z/frames/..."}at and range return one image per time. Times outside the recording are moved to its start or end.
curl "https://app.faylee.app/api/v1/recordings/V1StGXR8_Z/frames?mode=at&at=5000,12000&size=small" \ -H "Authorization: Bearer $FAYLEE_TOKEN"{ "mode": "at", "size": "small", "frames": [ { "t_ms": 5000, "url": "https://storage.example.com/..." }, { "t_ms": 12000, "url": "https://storage.example.com/..." } ]}A screenshot returns its own image as a single frame at t_ms 0. A replay’s frames are drawn on the server by rebuilding the captured page at each moment. Images, fonts and stylesheets the recorded site loaded from its own hosts are missing from them.
URLs are presigned and expire after one hour. Frames are made once and kept, so asking again for the same times is fast. The endpoint returns 422, with a message pointing at the steps, console and network instead, when there is nothing to draw: a replay with no captured page, or a replay that takes too long to draw.
Transcript
Section titled “Transcript”GET /recordings/:id/transcript returns what the reporter said while recording a video, as timed segments.
{ "status": "ready", "language": "english", "segments": [ { "startMs": 100, "endMs": 900, "text": "Here it breaks", "words": [{ "text": "Here", "startMs": 100 }] } ]}status is pending, ready or failed, or null when there is no transcript. segments is empty until the transcript is ready, and when the recording has no narration. Times are on the recording’s clock. words may be absent.
Attachments
Section titled “Attachments”GET /recordings/:id/attachments lists the files added to a recording, oldest first:
{ "attachments": [ { "id": 31, "filename": "server.log", "content_type": "text/plain", "byte_size": 48213, "url": "https://storage.example.com/..." } ]}url is a presigned download link that expires after one hour. A screenshot shared with marks drawn on it has an attachment named screenshot-without-marks.png: the same image without the marks, with blurred areas kept. The marks themselves are in the capture’s annotations.
POST /recordings/:id/attachments adds one. Send its metadata, then PUT the bytes to the returned upload_url with the same Content-Type.
| Field | Notes |
|---|---|
attachment.filename | Required, up to 200 characters |
attachment.content_type | Defaults to application/octet-stream |
attachment.byte_size | Greater than 0, at most 25 MB |
curl -X POST https://app.faylee.app/api/v1/recordings/V1StGXR8_Z/attachments \ -H "Authorization: Bearer $FAYLEE_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "attachment": { "filename": "server.log", "content_type": "text/plain", "byte_size": 48213 } }'{ "id": 31, "filename": "server.log", "upload_url": "https://storage.example.com/...?X-Amz-Signature=..." }curl -X PUT "$UPLOAD_URL" -H "Content-Type: text/plain" --data-binary @server.logA recording can have 10 attachments. The eleventh gets 422.
Email a recording
Section titled “Email a recording”POST /recordings/:id/emails emails the recording’s link to up to 10 people, from the token’s owner.
| Field | Notes |
|---|---|
emails | Email addresses. Duplicates and blanks are dropped |
note | Optional message included in the email |
curl -X POST https://app.faylee.app/api/v1/recordings/V1StGXR8_Z/emails \ -H "Authorization: Bearer $FAYLEE_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "emails": ["dana@example.com"], "note": "This is the checkout bug from standup" }'202 returns { "sent_to": ["dana@example.com"] }. Addresses are lowercased. An empty list, more than 10 addresses, or an invalid address gets 422.
Issues
Section titled “Issues”File an issue in one of the workspace’s connected issue trackers. See Issue trackers for connecting one.
List trackers
Section titled “List trackers”GET /integrations lists the trackers you can file to:
{ "trackers": [{ "id": 3, "provider": "linear", "name": "Linear" }], "recent": [], "available": [{ "provider": "github", "name": "GitHub" }], "connect_url": "https://app.faylee.app/settings/integrations", "can_connect": false}| Field | Notes |
|---|---|
trackers | Connected trackers that are ready to use. File with their id |
recent | Where the token’s owner filed last, with the fields they used |
available | Trackers the workspace has not connected |
connect_url | Where an admin connects one |
can_connect | Whether the token’s owner is an admin |
Tracker fields
Section titled “Tracker fields”GET /integrations/:id/fields returns the choices a tracker offers, by field. Which fields appear depends on the tracker. A field’s values are { value, label } pairs.
{ "assignee": [{ "value": "u_81", "label": "Dana Ruiz" }], "labels": [{ "value": "bug", "label": "bug" }], "priority": [{ "value": "1", "label": "Urgent" }]}The response is {} when the tracker could not be reached.
File an issue
Section titled “File an issue”POST /recordings/:id/issues:
| Field | Notes |
|---|---|
integration_id | Required. A tracker id from GET /integrations |
issue.title | The issue title |
issue.body | Markdown |
issue.fields.assignee | Optional. A value from the tracker’s fields |
issue.fields.priority | Optional |
issue.fields.labels | Optional. A list of values |
curl -X POST https://app.faylee.app/api/v1/recordings/V1StGXR8_Z/issues \ -H "Authorization: Bearer $FAYLEE_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "integration_id": 3, "issue": { "title": "Checkout fails with a 500", "body": "Paying with a saved card returns a 500 from /api/orders.", "fields": { "labels": ["bug"] } } }'201 means the issue was created:
{ "id": 58, "status": "created", "key": "WEB-142", "url": "https://linear.app/acme/issue/WEB-142" }Faylee adds a line with the recording’s share link to the end of the body.
202 with "status": "pending" means the tracker was down. Faylee keeps retrying, and key and url are null until it succeeds. When the tracker refuses the issue, the response is 422 with the tracker’s reason, and nothing is kept. The issue.created webhook is sent once the tracker has created the issue.
oEmbed
Section titled “oEmbed”GET /oembed returns an embeddable player for a share link. It lives at the site root, not under /api/v1, and takes no token.
| Parameter | Notes |
|---|---|
url | Required. A share or embed link, such as https://app.faylee.app/s/V1StGXR8_Z |
maxwidth | Optional. At most 960 |
maxheight | Optional. At most 540 |
format | Optional. Only json is supported |
curl -G https://app.faylee.app/oembed --data-urlencode "url=https://app.faylee.app/s/V1StGXR8_Z"{ "version": "1.0", "type": "rich", "provider_name": "Faylee", "provider_url": "https://app.faylee.app/", "title": "Checkout fails with a 500", "author_name": "Ann Park", "width": 960, "height": 540, "html": "<iframe src=\"https://app.faylee.app/embed/V1StGXR8_Z\" width=\"960\" height=\"540\" frameborder=\"0\" allow=\"fullscreen\" allowfullscreen></iframe>", "thumbnail_url": "https://app.faylee.app/s/V1StGXR8_Z/thumbnail", "thumbnail_width": 640}The player keeps a 16:9 shape inside the size you allow. thumbnail_url and thumbnail_width are present only when the recording has a thumbnail.
Only public recordings can be embedded. A recording that is not public, or whose share link has expired, gets 401. A link to another site or an unknown recording gets 404, and a format other than json gets 501. Links on a workspace’s verified link domain work too.
Share pages advertise this endpoint with a <link rel="alternate" type="application/json+oembed"> tag, so tools that support oEmbed discovery embed pasted share links on their own.