# Sluby documentation > Sluby is a self-hosted video platform that stores its media on the Sia network. Full text of every page: /llms-full.txt - Developers: API, SDK, player and how Sluby works - [Build with Sluby](/developers): Upload a video from your server, wait until it is ready, and play it in a React app. - **Guides** - [Upload videos from your app](/developers/guides/upload): Upload video files with @sluby/sdk or any tus client, track progress, and pause, resume or cancel an upload. - [Play videos in React](/developers/guides/play-react): Play Sluby videos with the SlubyPlayer component, stream them from Sia, and add quality menus and custom controls. - [Play videos without React](/developers/guides/play-hls): Play Sluby videos with hls.js in any web page, through the gateway or straight from Sia with the loaders from @sluby/player/hls. - [Serve private videos](/developers/guides/private-videos): Mint signed playback URLs on your server, play them in the browser, renew them during playback and revoke them. - [Manage your library from a server](/developers/guides/manage-library): List, search, read, edit, retry and delete videos with @sluby/sdk, and wait for processing to finish. - [Receive webhooks](/developers/guides/webhooks): Register a webhook endpoint, verify each request's signature, and use retries, the delivery log and test sends. - **Reference** - [API conventions](/developers/reference/api-conventions): Base URL, authentication, scopes, errors, pagination, rate limits, ETags, CORS and size limits that apply across the Sluby REST API. - REST API - Ops: Metrics and cache. - [Liveness probe.](/developers/reference/api/ops/get-health): Answers while the process is up; dependency health is GET /api/v1/system/status, so a flaky indexer never restarts the container. - [Platform metrics (prometheus registry as JSON).](/developers/reference/api/ops/get-metrics): The registry counts the whole process, so only an admin may read it. - [This OpenAPI document.](/developers/reference/api/ops/get-openapi-json) - [Interactive API reference.](/developers/reference/api/ops/get-docs) - [Library totals for the workspace.](/developers/reference/api/ops/get-stats): The same totals for every member and key. - [Plays per day of the workspace’s videos.](/developers/reference/api/ops/get-stats-usage): Plays of the workspace’s videos per UTC day for the last `days` days, today included, oldest first, with days without plays as 0. What counts as a play is described on `play_count` of an asset. - [Live platform status: database, redis, Sia indexer, reconciliation, cache.](/developers/reference/api/ops/get-system-status): Admins get the full report; members a health summary (see SystemStatus). - [Platform metrics (prometheus registry as JSON).](/developers/reference/api/ops/get-metrics): The registry counts the whole process, so only an admin may read it. - [Delivery cache statistics.](/developers/reference/api/ops/get-cache-stats) - Auth: Studio sign-in, setup, invite and reset links. - [Begin a Studio sign-in with a provider.](/developers/reference/api/auth/get-auth-provider-login): Sets the short-lived `sluby_signin` cookie (path /api/v1/auth) holding the handshake state and where to land afterwards, then redirects to the provider. `next` is a path on this server; anything that would leave the origin falls back to /studio. - [The provider's redirect back after sign-in.](/developers/reference/api/auth/get-auth-provider-callback): Browser-facing: every outcome is a redirect into the Studio. Accepted only from the browser that started the sign-in: the `state` must match the `sluby_signin` cookie set by the login route, within ten minutes. The account is the provider's stable id (GitHub: the numeric account id), never the login. A failure redirects to the page the sign-in started from (or `/studio` once the handshake is lost) with `signin_error=` added: `expired_handshake` (expired, or started in another browser), `not_a_member` (with `&handle=`, nobody added this account; people join through invites), `disabled` (with `&handle`, an admin removed the member), `already_linked` (with `&handle`, joining or linking with an account another member already signs in with), `not_signed_in` (a link finished without the session it began in), `invite_not_found`, `invite_used`, `invite_expired`, `invite_revoked` (with `&handle`, joining with an invite that does not work), `cancelled` (the person declined at the provider), `provider_failed` (the provider refused the code or could not be reached) or `not_configured`. A join signs the new member in; a link keeps the current session. - [How this server signs people in.](/developers/reference/api/auth/get-auth-config): Public: the Studio reads it before anyone is signed in, to show the setup screen or the sign-in methods. - [Set up the first admin with the setup code, and sign in.](/developers/reference/api/auth/post-auth-setup): Works while `setup_required` is true: no admin who can sign in exists. The code is printed in the server log at startup. 10 attempts per client address in 10 minutes. - [Sign in with an email and password.](/developers/reference/api/auth/post-auth-password): 10 attempts per client address in 10 minutes, and 5 wrong passwords per email in 15 minutes. - [Change the signed-in member's password.](/developers/reference/api/auth/post-auth-password-change): Studio sessions only. Every other session of the account ends; this one stays. `revoked` is how many ended. - [What an invite or reset link is for.](/developers/reference/api/auth/get-auth-invites-token): Public: the join and reset pages call it with the token from their url. 30 link requests per client address in 10 minutes. - [Use an invite or reset link, and sign in.](/developers/reference/api/auth/post-auth-invites-token-accept): An invite makes a new member with its role. A reset link sets the member’s password (their first, when they had none) and ends every other session of the account. Either way the link is used up and the browser is signed in. - [The signed-in account.](/developers/reference/api/auth/get-auth-me): The account id and display name, plus the provider, provider label and handle the browser signed in with, its role, every way it signs in (`sign_in`), and the session expiry. With AUTH_DISABLED it is the local dev account (`provider: "local"`, `auth_disabled: true`, `role: "admin"`). - [Change the signed-in member's name.](/developers/reference/api/auth/patch-auth-me): Studio sessions only. The name the members list and the Studio show. - [Change the email the signed-in member signs in with.](/developers/reference/api/auth/post-auth-email-change): Studio sessions only. Needs the current password; the password itself stays. Counts against the same attempt limits as a password change. - [Stop signing in with a provider.](/developers/reference/api/auth/delete-auth-identities-provider): Studio sessions only. Takes a provider such as GitHub off the signed-in account. Sessions that signed in through it end. An email and password is changed, not removed. - [End the current session and clear its cookie.](/developers/reference/api/auth/post-auth-logout) - [The signed-in user's live Studio sessions, newest first.](/developers/reference/api/auth/get-auth-sessions): Studio sessions only: an API key cannot list sessions. - [Sign out everywhere: end every session of the signed-in account.](/developers/reference/api/auth/delete-auth-sessions): API keys are not touched; revoke them from the keys routes. - [End one session of the signed-in user.](/developers/reference/api/auth/delete-auth-sessions-id) - Uploads: Resumable upload sessions (TUS). - [Start a resumable upload (tus 1.0.0 creation).](/developers/reference/api/uploads/post-uploads): Creates the video asset and answers with the upload url in Location: `/api/v1/uploads/`. The upload id (a uuid) is also the id of GET and DELETE /api/v1/uploads/{id}. Send the bytes with PATCH to that url; a HEAD on it returns Upload-Metadata with videoAssetId. Metadata keys (values base64): filename, filetype (must be video/* when given), title (up to 200 characters, "Untitled" when missing), description (up to 5000 characters), accessTier or access_tier (public or private, default public). Errors answer plain text. - [Upload status.](/developers/reference/api/uploads/get-uploads-id) - [Send upload bytes (tus).](/developers/reference/api/uploads/patch-uploads-id) - [Cancel an upload in progress.](/developers/reference/api/uploads/delete-uploads-id): Without Tus-Resumable this is the REST cancel: the partial file and the asset the upload created are removed, and the session is marked cancelled (200). With Tus-Resumable it is a tus termination, which does the same and answers 204. A finished upload is the source its asset is processed and retried from and cannot be cancelled: REST answers 409, tus 400; delete the asset instead (manage scope). - [Upload offset and metadata (tus).](/developers/reference/api/uploads/head-uploads-id) - Assets: Video asset lifecycle and metadata. - [List assets with search, filters, sorting and pagination.](/developers/reference/api/assets/get-assets): Each parameter may be given once; a repeated or malformed one is a 400. Unknown parameters are ignored. - [Get an asset.](/developers/reference/api/assets/get-assets-id) - [Update asset metadata.](/developers/reference/api/assets/patch-assets-id): Changing access_tier takes effect on the delivery gateway at once. An asset being deleted answers 404, as it does to every read. Send If-Match with the ETag of the version you edited (from GET or an earlier PATCH, or built from `updated_at` as `""`) and the edit applies only if nobody changed the asset since; otherwise 412 with the asset as it is now. Processing changes the version too. - [Delete an asset.](/developers/reference/api/assets/delete-assets-id): Soft-deletes the asset so it disappears from the API immediately, then asynchronously unpins its Sia objects and removes the record. A second DELETE answers 404. - [Processing status for an asset.](/developers/reference/api/assets/get-assets-id-processing) - [HLS renditions of an asset, tallest first.](/developers/reference/api/assets/get-assets-id-renditions) - [Retry a failed asset.](/developers/reference/api/assets/post-assets-id-retry) - Storage: Per-asset Sia storage breakdown. - [Per-asset Sia storage breakdown.](/developers/reference/api/storage/get-assets-id-sia): Objects the indexer cannot describe right now are listed in unavailable_object_ids rather than failing the request. - Playback: Playback URLs and playback IDs. - [List playback IDs for an asset.](/developers/reference/api/playback/get-assets-id-playback-ids) - [Create a playback ID.](/developers/reference/api/playback/post-assets-id-playback-ids) - [Delete a playback ID.](/developers/reference/api/playback/delete-playback-ids-playbackid) - [Playback info + URL for an asset or playback ID.](/developers/reference/api/playback/get-playback-id) - [Time-limited signed playback URL.](/developers/reference/api/playback/get-playback-id-signed): Signs the master manifest (and the first thumbnail as a poster) for the asset. The signature covers the asset signing generation, so every url minted so far stops working when the asset tier changes or POST /api/v1/playback/{id}/signed/revoke is called. A url never lasts longer than six hours. - [Revoke every signed URL of an asset.](/developers/reference/api/playback/post-playback-id-signed-revoke): Raises the asset signing generation, so every signed url minted for it so far (and every child url signed from one) is refused from now on; new ones can be minted straight away. A pb_ id revokes for its whole asset. Copies of public objects a shared cache already holds are unaffected, and sia:// share maps run until their own expiry. - [Per-object sia:// share URLs for direct-from-Sia playback.](/developers/reference/api/playback/get-playback-id-share) - Members: Members, roles and invites (admins only). - [Every member of the workspace, removed ones included.](/developers/reference/api/members/get-members) - [Change a member's role.](/developers/reference/api/members/patch-members-id): Applies to their sessions and keys on their next request. The last admin who can sign in stays an admin (409 `last_admin`). - [Remove a member.](/developers/reference/api/members/post-members-id-remove): They cannot sign in, every session they have ends and their keys stop, until they are restored. Nobody removes themselves (409 `self`), and the last admin who can sign in stays (409 `last_admin`). - [Let a removed member back in.](/developers/reference/api/members/post-members-id-restore): They keep the role they had, and their keys work again. - [A link where the member sets a new password.](/developers/reference/api/members/post-members-id-reset-link): Single use, valid for 7 days. For a member who signs in with GitHub only it sets their first password, with an email they choose. Using it ends their other sessions. - [Pending invites, newest first.](/developers/reference/api/members/get-invites): Invites that still let someone join, with how many people joined with each. Their links are not shown. - [Invite new people with a role.](/developers/reference/api/members/post-invites): By default the link lets one person join and works for 7 days. `max_uses` lets more people join with it (members only) and `expires_in_days` sets 1, 7 or 30 days. Sluby sends no email: pass the link on yourself. - [Withdraw a pending invite.](/developers/reference/api/members/delete-invites-id) - Keys: Scoped API key management. - [List API keys.](/developers/reference/api/keys/get-keys): Usable keys of the workspace, newest first, with who made each, including rotated-out keys still in their grace period. Pages hold up to 1000 keys; follow next_cursor while has_more is true. - [Create an API key.](/developers/reference/api/keys/post-keys): The key belongs to the workspace and is made by the calling member, whose role it acts with (`created_by`); a key with no creator makes keys with no creator. The answer has the shape of a listed key plus `key`, the secret, which is never shown again. - [Rotate an API key.](/developers/reference/api/keys/post-keys-id-rotate): Issues a new secret with the same name, scopes and rate limit. The old key stops working now, or after grace_period_seconds (up to 604800) for a zero-downtime switch. - [Rename an API key.](/developers/reference/api/keys/patch-keys-id): Changes only the name; the secret, scopes and rate limit stay. An unknown key, a revoked key and a key past its grace period answer 404. - [Delete an API key.](/developers/reference/api/keys/delete-keys-id) - Webhooks: Event delivery endpoints. - [List webhook endpoints.](/developers/reference/api/webhooks/get-webhooks): Every endpoint of the workspace, paused ones included, newest first. Needs `manage`, like creating them: receiver URLs often carry credentials. - [Register a webhook endpoint for the workspace.](/developers/reference/api/webhooks/post-webhooks): Events are delivered for the workspace’s assets. Each request carries X-Sluby-Event, X-Sluby-Delivery (the same on every retry of one delivery), X-Sluby-Timestamp (Unix seconds, per attempt) and X-Sluby-Signature-V1, the hex HMAC-SHA256 of `.` with the endpoint secret; receivers should refuse a timestamp more than five minutes from their clock. X-Sluby-Signature (the HMAC of the body alone) is still sent but deprecated, since it does not bind a time. The body is `{ id, event, timestamp, data }`, where `id` identifies the event across retries. The URL must be http(s) and its host must resolve only to public addresses (or to the operator's WEBHOOK_ALLOWED_NETWORKS); this is checked again before every delivery, redirects are not followed and response bodies are not read or stored. The secret is returned once. - [Send a signed `webhook.test` event to the endpoint now.](/developers/reference/api/webhooks/post-webhooks-id-test): Delivered once, signed like any event, and recorded with the deliveries. Answers how the receiver responded: `delivered`, `status_code` (null when no response came) and `error` (why it failed, in our words). - [Replace the signing secret of a webhook endpoint.](/developers/reference/api/webhooks/post-webhooks-id-rotate-secret): For a secret that leaked. The old secret stops at once: every request from now on, retries of earlier events included, is signed with the new one, which is returned only here. - [The endpoint's latest delivery attempts, newest first.](/developers/reference/api/webhooks/get-webhooks-id-deliveries): One entry per attempt: id, event_id, event, status_code, delivered, error, attempt (1 for the first try), created_at, the payload that was sent, and manual (true for a resend). Response bodies are never stored. Attempts are kept for 30 days. - [Send a logged delivery again.](/developers/reference/api/webhooks/post-webhooks-id-deliveries-deliveryid-redeliver): Queues the stored payload of the delivery again, with the same event `id` so a receiver that already handled it can drop the repeat. It is signed with the current secret, gets a new X-Sluby-Delivery id and the usual retries, and each attempt is logged with `manual: true`. A paused endpoint answers 409. - [Get a webhook endpoint.](/developers/reference/api/webhooks/get-webhooks-id) - [Edit, pause or resume a webhook endpoint.](/developers/reference/api/webhooks/patch-webhooks-id): Send any of `url`, `events` and `is_active`. A new URL is checked like at registration. `is_active: false` pauses the endpoint: nothing is sent to it, and deliveries already queued for it are dropped. `is_active: true` resumes it for new events. The signing secret does not change. - [Delete a webhook endpoint.](/developers/reference/api/webhooks/delete-webhooks-id) - Reconciliation: DB vs indexer inventory drift. - [Latest reconciliation run.](/developers/reference/api/reconciliation/get-reconciliation): Admins get the whole run, orphaned ids included; members get only the missing objects of the workspace’s assets (see ReconciliationRun). - [Trigger a reconciliation run.](/developers/reference/api/reconciliation/post-reconciliation-run): A run scans the whole deployment, so only an admin may start one. It is queued unless one, manual or scheduled, is already waiting or running, so repeated calls never stack runs. - Delivery: Byte-range gateway for Sia objects. - [Stream a Sia object, with byte-range support.](/developers/reference/api/delivery/get-objects-objectid): An object of a public asset needs nothing. An object of a private asset needs the `expires` and `sig` of a signed url (GET /api/v1/playback/{id}/signed); a manifest served that way has the urls of its children signed with the same expiry. Access follows only the asset tier: a signed playback ID gates its own /v1/stream entry, not these objects. HEAD answers the same headers without a body. A public answer carries `ETag` (the object id) and `Cache-Control: public, no-cache`, and a conditional request (`If-None-Match`) is answered 304 only after access is checked again, so a cache never keeps serving an object after its asset turns private or is deleted; the shipped nginx keeps small public objects for at most 60 seconds between checks. Objects over 8 MB are streamed and never cached. - [Master HLS manifest for an asset or playback ID.](/developers/reference/api/delivery/get-stream-videoassetid-master-m3u8): A private asset or a signed-policy playback ID needs the `expires` and `sig` of the asset’s signed master url; the child urls in the answer are then signed too. A signed playback ID gates only this entry: on a public asset its objects stay public, so the bytes are not private (make the asset private for that), and other embeds of the asset keep playing. A public answer carries an ETag and `public, no-cache`, but no shared cache keeps it: each 200 GET counts as a play (see `play_count` on an asset); HEAD and 304 do not. - [Per-object sia:// share URLs for direct-from-Sia playback, without credentials.](/developers/reference/api/delivery/get-share-id): Gated like /v1/stream: a public asset by its id or a public playback ID needs nothing. A private asset or a signed-policy playback ID needs the `expires` and `sig` of its signed master URL, and the shares then never outlive `expires`. Expiries are capped like the authenticated route (six hours, one hour when gated) and rounded to five-minute slots that the server caches, so repeated calls do not mint again. Every map is sent `no-store` (`private, no-store` when gated): minting one is how a direct-from-Sia play reaches the server, and each counts as a play (see `play_count` on an asset), unless `renew` is set. Share URLs already handed out keep working until they expire, whatever happens to the asset meanwhile. - [A video's public watch page, for a link or an iframe.](/developers/reference/api/delivery/get-watch-id): An HTML page that plays the video by asset ID or pb_ playback ID through the gateway. A private video or a signed playback ID needs the `expires` and `sig` of a signed URL, which the page passes on to the stream and the poster. Any site may frame it (`frame-ancestors *`). Query `autoplay=1`, `muted=1` and `loop=1` change how it starts. - [The watch page's player script, hls.js build and stylesheet.](/developers/reference/api/delivery/get-watch-assets-file) - [@sluby/sdk reference](/developers/reference/sdk): Every option, namespace, method, error class and exported type of the Sluby TypeScript SDK. - [@sluby/player reference](/developers/reference/player): Every export, prop, callback, handle method and loader of the Sluby React player and its hls.js entry. - [Webhook events](/developers/reference/webhook-events): Every webhook event Sluby sends, its payload, the request headers, the signature scheme, retries and the delivery log. - **How it works** - [Architecture](/developers/concepts/architecture): The parts of a Sluby server, what each one does, and how a request travels between them. - [Upload pipeline](/developers/concepts/upload-pipeline): How a video goes from a resumable tus upload through FFmpeg to objects stored on Sia, and how each stage retries. - [Storage on Sia](/developers/concepts/storage-on-sia): How Sluby stores each video as Sia objects, how erasure coding spreads them over hosts, and what the storage breakdown reports. - [Playback and delivery](/developers/concepts/playback): How video bytes reach a viewer, through the gateway, straight from Sia or both, and how signed URLs, share URLs, playback ids, the watch page and caching work. - [Deletion and reconciliation](/developers/concepts/deletion-and-reconciliation): How Sluby deletes a video and its Sia objects, and how it checks its database against the Sia indexer. - [Data model](/developers/concepts/data-model): The PostgreSQL tables a Sluby server keeps, what each one holds, and how they relate. - [Accounts, roles and sessions](/developers/concepts/accounts-and-roles): How Sluby models a team, what admins and members can do, and how sign-in, sessions, invites and reset links work. - [Security model](/developers/concepts/security): What Sluby exposes without authentication, how it authenticates requests, and how it protects keys, sessions, signed URLs, cross-origin calls, webhooks and logs. - **AI agents** - [Use Sluby with AI agents](/developers/ai-agents): Install the Sluby agent skill in your coding agent, and point agents at the plain-text versions of these docs. - Self-hosting: Install and run a Sluby server - [Install Sluby](/self-hosting): Run Sluby on one machine with Docker Compose, connect it to Sia, and create the first admin. - [Connect Sia storage](/self-hosting/sia-storage): Register your Sluby server with a Sia indexer and put its app ID and key in .env. - [Run in production](/self-hosting/production): Serve Sluby on your domain over HTTPS with the production override and Caddy. - [Set up sign-in](/self-hosting/sign-in): Create the first admin with the setup code, add GitHub sign-in, and make reset links from the command line. - [Stream from Sia in the browser](/self-hosting/direct-playback): Give the Studio player a viewer identity so viewers' browsers fetch video bytes from Sia. - [Monitor and maintain](/self-hosting/operations): Check health, read logs, scrape metrics, run a storage check, and upgrade a Sluby server. - [Troubleshooting](/self-hosting/troubleshooting): Errors and log lines a Sluby server produces, what each one means, and how to fix it. - **Reference** - [Configuration reference](/self-hosting/configuration): Every environment variable of every Sluby service, with its default and what it does. - Studio: Upload, share and manage videos - [Get started](/studio): Open the Studio, create your account, upload your first video, watch it and copy its link. - **Videos** - [Upload a video](/studio/upload): Add video files in the Studio, give each a title, choose who can watch, and pause, resume or cancel the upload. - [Follow processing and retry](/studio/processing): See where a video is in processing, read its processing log, and retry a video that failed. - [Find and edit videos](/studio/library): Search, filter and sort your videos on the Videos page, preview them, and edit a video's title and description. - [Watch a video](/studio/watch): Play a video in the Studio, pick a quality, and read the badge that shows where the video streams from. - [Share or embed a video](/studio/share): Copy a video's watch page link or embed code from the Studio, and share a private video with a signed link. - [Choose who can watch](/studio/privacy): Make a video public or private, manage its playback IDs, and stop links you already shared. - [See how a video is stored on Sia](/studio/storage): Read the Storage tab of a video, which shows the files, hosts and countries that hold it on Sia. - [Delete videos](/studio/delete): Delete one video or many from the Studio, and what happens to the video's files and links. - **Team and account** - [Sign in and out](/studio/sign-in): Sign in to the Studio with your email and password or with GitHub, join from an invite link, set a new password from a reset link, and sign out. - [Manage your account](/studio/account): Change your name, email and password, link or remove GitHub, sign out other browsers and pick a theme from the Settings page. - [Invite and manage members](/studio/members): Invite people to your Studio, change their role, remove or restore them, and give them a link to set a new password. - [Create and manage API keys](/studio/api-keys): Create an API key for your server, copy it once, and rename, rotate or revoke keys from the Studio. - [Overview, health and metrics](/studio/overview): Read the Overview page, and as an admin, the Health and Metrics pages.