انتقل إلى المحتوى

HTTP API

The web interface uses this API, and so can you.

  • Endpoints under /api take and return JSON, except the three upload endpoints (…/blob, …/parts/{n}, …/thumbnail), which take the raw bytes.
  • Authentication is the vistilla_session cookie set by POST /api/auth/login (or by finishing sign-up).
  • X-Vistilla-Lang: ar (or en), sent by the site and the app with every request, picks the language of the emails a request sends (invitations, shared videos, sign-up codes, password resets); their links then carry ?lang=ar.
  • Errors look like {"error": "message"}, with the status codes listed in Backend → Errors.

Legend:

  • 🔓 public: no session needed;
  • ◐ optional: works with or without a session;
  • everything else needs a session.

Accounts

Method Path Notes
POST /api/auth/register 🔓 {email, name, password, org_name?, invite_token?}. Without an invite this emails a 6-digit code and answers {verify: true, email}. With a valid invite it creates the account and signs in.
POST /api/auth/register/verify 🔓 {email, code}. Creates the account and workspace, and signs in.
POST /api/auth/login 🔓 {email, password}.
POST /api/auth/logout 🔓 Ends the current session.
POST /api/auth/forgot 🔓 {email}. Emails a reset link. Answers {ok, found, email_configured, message}.
GET /api/auth/reset/{token} 🔓 Is this reset link still valid?
POST /api/auth/reset 🔓 {token, password}.
GET /api/me Me, my workspaces and teams with roles.
PATCH /api/me {name?, current_password?, new_password?}.
PUT, DELETE /api/me/avatar Set (JPEG, PNG or WebP body, up to 512 KB) or remove my photo.
GET /api/users/{id}/avatar?v=… 🔓 A person's photo, cached for a year; the v changes with each new photo. Wherever a person appears next to their avatar_hue, their photo's address (or null) comes too: avatar_url on /api/me's user, members, comment authors and a video's uploader; actor_avatar_url on notifications; viewer_avatar_url in insights.
GET /api/workspace-name?name= 🔓 Is a workspace name free?
GET /api/team-name?name= Can a workspace (a team on screen) be given this name? {available, reason}: one word, not used anywhere on Vistilla.

Workspaces, teams and members

Method Path Notes
GET, POST /api/orgs List, or create a workspace (a team on screen) {name?, team_name?} (when sign-up is open, or for anyone who can record in a workspace). Its first team (a channel on screen) is team_name, else "General".
GET /api/orgs/{org_id}/storage Space used: {workspace_bytes, workspace_videos, total_bytes, quota_bytes} (any member).
DELETE /api/orgs/{org_id} Deletes the workspace (a team on screen) with its teams, recordings and their files. Its owner only; 409 when it is the last workspace they are in.
PATCH /api/orgs/{org_id} {accent}: the team's color, one of teal, blue, indigo, violet, pink, red, orange, green, slate (teal clears it). Owners and admins. GET /api/me gives each workspace's accent.
GET, POST /api/orgs/{org_id}/teams List or create teams (channels on screen; create: workspace admin+). Names are unique inside the workspace.
GET, PATCH, DELETE /api/teams/{team_id} Read, rename or delete a team. Names are unique in a workspace, ignoring case (409 otherwise). DELETE (workspace admin+, or the team's owner) removes its recordings and their files too, and is refused (409) for a workspace's last team.
GET /api/teams/{team_id}/members Members and roles.
PATCH, DELETE /api/teams/{team_id}/members/{user_id} Change a role, or remove someone.
GET, POST /api/teams/{team_id}/invites List, or send an invitation (admin+): {email, role?}.
GET /api/invites/{token} 🔓 Look at an invitation.
POST /api/invites/{token} Accept it.
POST /api/invites/{token}/decline 🔓 Decline it.
POST /api/invites/{invite_id}/revoke Revoke a pending invitation.

Collections

Method Path Notes
GET, POST /api/teams/{team_id}/collections List (with can_add, and can_manage on each) or create. Creating, renaming and deleting are for the channel's owners.
PATCH, DELETE /api/collections/{collection_id} Rename or delete (owners). Videos stay.
GET, PUT, DELETE /api/collections/{collection_id}/image The collection's picture, shown on its folder: get it (anyone in the channel), set it (owners; the body is a JPEG, PNG or WebP of at most 1 MB) or remove it (owners). The list gives each collection's image_url, or null.

Videos

Method Path Notes
GET /api/videos?team_id=&collection_id=&q=&tag=&mine=&loose=&sort=&limit=&offset= The library, with search. sort is new (the default), old, az, za (by title; a title starting with a number goes by that number), long, short or views. loose=true (without collection_id) leaves out videos that are in a collection, as All recordings does. limit defaults to 40, at most 100. Each video has uploaded_bytes: while status is uploading, the size of the parts that have arrived; after, the whole size.
POST /api/videos Reserve a video: {team_id, title?, content_type?, size_bytes?, visibility?, collection_id?, live?, reply_to_comment_id?}. Returns the upload URLs: proxy_put_url (through the server), direct_put_url (presigned, straight to the bucket; S3 only), and part_url (uploads in parts, live: true), plus video_id, complete_url, content_type, and the limits max_upload_bytes (one request) and max_recording_bytes (in parts).
PUT /api/videos/{id}/blob Upload the whole file.
PUT /api/videos/{id}/parts/{n} Upload part n of a live upload.
POST /api/videos/{id}/cut {start_ms?, end_ms?}: cuts a published recording (editors only) down to that part, for good. The video is processing until the worker has cut it; comments, chapters, reactions and transcript lines then move to the new timeline. 409 while it is not ready or its transcript or summary is being written; 400 for a cut that keeps everything or under a second, or a recording too large to cut.
POST /api/videos/{id}/complete {duration_ms?, width?, height?, size_bytes?, title?, trim_start_ms?, trim_end_ms?, partial?}; partial: true publishes the parts that arrived, up to the first missing one.
PUT /api/videos/{id}/thumbnail Poster image.
GET, PATCH, DELETE /api/videos/{id} Read, edit (title, description, tags, collection_id, team_id (move to another channel of the same team; it leaves its collection), visibility, ctas: up to 5 link buttons [{label, url, icon?}], icon a picked logo as a small PNG/JPEG/WebP/GIF data: URL, an empty list removes them; the older single cta_label + cta_url still works), or delete. A video carries ctas (every button) and cta (the first).
POST /api/videos/bulk-delete {ids: [...]}, up to 200.
GET, POST /api/videos/{id}/chapters List or add chapters.
DELETE /api/chapters/{chapter_id} Remove a chapter.

Comments, reactions and moments

Method Path Notes
GET, POST /api/videos/{id}/comments {body, timecode_ms?, parent_id?, mentions?, images?}. images: up to 4 pictures as data:image/…;base64, URLs (JPEG, PNG, WebP or GIF, 4 MB each; the request may be 24 MB); body may then be empty. Each comment lists its images as addresses.
PATCH, DELETE /api/comments/{id} Edit (author) or delete.
POST /api/comments/{id}/resolve {value: true\|false}.
POST /api/comments/{id}/pin {value: true\|false}: pinned to the top of the thread.
DELETE /api/comments/{id}/moment Takes the comment off its moment (timecode_ms becomes null). Author, the video's owner or an admin.
PUT /api/comments/{id}/moment {timecode_ms}: pins the comment to that moment, or moves it there (clamped to the video). Not for replies. Author, the video's owner or an admin.
POST /api/comments/{id}/reactions {emoji}. Same emoji again removes it.
GET, POST /api/videos/{id}/moments Emoji on the timeline: {emoji, timecode_ms}. One per person per video: a new one replaces theirs, and the same emoji again removes it (answers emoji: null).

Sharing

Method Path Notes
GET, POST /api/videos/{id}/shares List or create links: {password?, expires_in_days?, allow_download?, allow_comments?}.
POST /api/videos/{id}/invites {emails: [...], message?}: personal links by email (up to 20).
PATCH, DELETE /api/shares/{share_id} Change options {allow_download?, allow_comments?, expires_in_days?, password?}, or revoke.
GET /api/share/{token} ◐ The shared video, or {locked: true}.
POST /api/share/{token}/unlock 🔓 {password}. Sets a 12-hour unlock cookie.
GET, POST /api/share/{token}/comments ◐ Guest comments: {body, timecode_ms?, parent_id?, guest_name?}.
POST /api/share/{token}/comments/{comment_id}/reactions ◐ Guest reactions.
GET, POST /api/share/{token}/moments ◐ Guest moments.
POST /api/share/{token}/views ◐ Report viewing progress: {session_key, watched_ms?, completed?, guest_name?}.

Transcripts, summaries and AI settings

Method Path Notes
GET, POST /api/videos/{id}/transcript Read it (with segments and captions_url), or queue it again. ?lang=en or ?lang=ar reads it in that language: see Translation in the AI pipeline.
GET, POST /api/videos/{id}/summary Read it, or queue it again. Takes ?lang= as the transcript does.

GET /api/videos/{id}/chapters?lang= names the AI's chapters in that language, from the summary's translated topics (translation: "pending" while it is made). | GET | /api/search/transcripts?team_id=&q= | Search spoken words (at least 2 characters, 50 results). | | GET | /api/orgs/{org_id}/ai | Providers on offer and the workspace's settings (admin+). | | PUT, DELETE | /api/orgs/{org_id}/ai/{purpose} | purpose is transcription or summary. Body: {vendor, model, api_key?, base_url?}. | | POST | /api/orgs/{org_id}/ai/{purpose}/test | Check the key works. |

Notifications and insights

Method Path Notes
GET /api/notifications?unread_only=&limit= Activity, default 30.
POST /api/notifications/read {ids?} or {video_id}. No ids means everything; video_id marks the notices about that video's comments (not watched). Answers the new unread count.
GET /api/videos/{id}/analytics Insights (uploader or admin+).
POST /api/videos/{id}/views {session_key, watched_ms?, completed?}.
GET /api/healthz 🔓 {"status": "ok"}.

Media (outside /api)

Method Path Notes
GET /media/videos/{id} Stream, with Range support or a 307 to the bucket.
GET /media/videos/{id}/download Download with a file name.
GET /media/comment-images/{team}/{file} A comment's picture. Its name has a long random part and is only given out with the comment, so it needs no session (a shared link's guests see them).
GET /media/thumbs/{id} Poster image. thumbnail_url adds ?v= naming the picture, so a new one (PUT /api/videos/{id}/thumbnail, the owner) is fetched afresh; each picture is stored under a name of its own and the one it replaces is deleted.
GET /media/favicon/{host} A website's small logo, for its link button: a logo remembered for the site (site_logos) first, then the icon its home page names (<link rel="icon">), else /favicon.ico, else Google's favicon service. The server fetches it, public addresses only, so viewers' browsers do not; cached for a week. Signed-in callers may ask for any site; others only for one a recording links to.
GET /media/videos/{id}/captions.vtt Captions.
GET /media/share/{token} ◐ Stream through a share link (the token, and the unlock cookie for a password-protected link, grant access).
GET /media/share/{token}/download ◐ Only if the link allows downloads.
GET /media/share/{token}/captions.vtt 🔓 Captions through a share link.

Pages and files

Path Serves
/, /app, /record, /settings, /tour Pages.
/v/{id}, /s/{token} Video and share pages, with link-preview tags.
/join/{token}, /reset/{token} Invitation and password-reset pages.
/preview/v/{id}, /preview/s/{token} Poster image for link previews.
/assets/* Scripts, styles, icons.
/docs, /docs/* This documentation (/docs redirects to /docs/).
/sw.js, /favicon.svg Service worker, icon.

Example: upload a file with curl

B=https://vistilla.example.com
# Sign in and keep the cookie.
curl -c jar -H 'content-type: application/json' \
  -d '{"email":"me@example.com","password":"…"}' $B/api/auth/login
# Find your team id.
curl -b jar $B/api/me
# Reserve, upload, complete.
curl -b jar -H 'content-type: application/json' \
  -d '{"team_id":"tm_…","title":"Demo","content_type":"video/mp4","size_bytes":1234567}' \
  $B/api/videos                       # → {"video_id": "vid_…", "proxy_put_url": …}
curl -b jar -X PUT -H 'content-type: video/mp4' --data-binary @demo.mp4 \
  $B/api/videos/vid_…/blob
curl -b jar -H 'content-type: application/json' \
  -d '{"duration_ms":30000,"width":1920,"height":1080}' \
  $B/api/videos/vid_…/complete