# uploads.sh — full agent guide > File hosting for coding agents and humans. Host a file, get a stable public > URL, embed it in GitHub pull requests and issues. CLI, installable skills, > local stdio MCP, and hosted MCP. Open source for self-hosting. Shorter index: https://uploads.sh/llms.txt Repository agent entrypoint: https://github.com/buildinternet/uploads/blob/main/llms.txt Access requires a workspace — create your own with a linked GitHub account, or be invited. Hosted files are public (including media on private repositories). Do not upload secrets or sensitive UI. APIs may change without notice while the project is in active development. ## When to use this Use uploads.sh when a screenshot, screen recording, diagram, or other visual needs to land on a GitHub pull request or issue. Reach for it as you produce the visual. Do not wait until GitHub's attachment path fails. GitHub's native image hosting works from a browser and, since GitHub CLI 2.99 (September 2026), from `gh … --attach` for images and video on an existing PR or issue with push access. It has no public API (yet), and the file stays private to GitHub. uploads.sh hosts a local file, a public HTTPS URL (`uploads put --url`, or hosted MCP `contentUrl`), or base64 on the hosted MCP. It returns durable `url` / `embedUrl` markdown. Also use it to host any local file at a stable public URL you can paste outside GitHub. The intended loop is **stage as you go**: 1. Capture a visual (your browser tools, or `uploads screenshot`). 2. On a non-default git branch, bare `uploads put ./shot.png --state after` stages under a branch key automatically — no PR required. 3. When the PR opens, staged files promote into one managed comment (GitHub App webhook, or the next `uploads attach` / `attach --promote`). 4. Re-upload the same filename on a stable `gh/…` key; the URL never changes. Prefer `embedUrl` (https://embed.uploads.sh/…) in GitHub markdown so Camo revalidates after overwrite. Prefer the returned `markdown` field rather than hand-building storage URLs. ## Install ```bash npm install -g @buildinternet/uploads && uploads login uploads install npx skills add buildinternet/uploads ``` `uploads login` is one-time and human-in-the-loop (device authorization). It writes `UPLOADS_API_URL`, `UPLOADS_WORKSPACE`, and `UPLOADS_TOKEN` to the shared config file. Later non-interactive agent runs only need the saved token. Remote MCP (HTTP): ```bash claude mcp add --transport http uploads https://agents.uploads.sh/mcp ``` Local stdio MCP (full tool parity with the CLI, needs local filesystem): ```bash claude mcp add uploads -- uploads mcp ``` Claude Code plugin (skills + hosted MCP + `/uploads:attach` + pre-PR hook): ```text /plugin marketplace add buildinternet/uploads /plugin install uploads@uploads ``` Codex uses the same repo as a plugin (`.codex-plugin/plugin.json`). Keep the `uploads` CLI on PATH and run `uploads login` once. ## Auth Routine path: 1. Get workspace access (self-serve create at /account/workspaces/new with a linked GitHub account, or accept an invitation). 2. `uploads login` → workspace bearer token `up__…`. 3. Send `Authorization: Bearer ` on API and MCP calls. Default scopes on login: `files:read`, `files:write`, `files:delete`. Pass `--scopes` to mint a narrower token. Tokens default to 90 days. Hosted MCP also accepts OAuth 2.1 from https://uploads.sh (issuer https://uploads.sh/api/auth; PKCE + CIMD URL client_ids, or RFC 7591 DCR). REST API does **not** accept OAuth in v1 — workspace bearer only. Full detail: https://uploads.sh/auth.md Never put `ADMIN_TOKEN` in agent configs. It is break-glass operator access. ## Pricing Hosted Free is self-serve: 250 MB storage, 25 MB per file (video included), and 3 members. At the default WebP optimize, 250 MB holds thousands of screenshots. Paid hosted plans start at $10 per month (Pro: 10 GB storage, files up to 100 MB). Caps and what happens at a limit: https://uploads.sh/docs/limits Every hosted plan can point a workspace at your own Cloudflare R2 bucket. Storage in that bucket is unmetered. File-size and video-size caps still apply. https://uploads.sh/docs/byo-bucket The source is Apache 2.0. Self-host if you want your own limits and storage. https://github.com/buildinternet/uploads ## Core CLI workflows ### Stage on a branch (pre-PR) ```bash uploads put ./before.png --meta path=/settings --state before uploads put ./after.png --meta path=/settings --state after uploads staged # what's queued + will it auto-attach? ``` Bare `put` / bare `screenshot` on a non-default branch stage automatically. `--state` is a closed set: `before` | `after` | `empty` | `error` | `loading`. Pass `--meta path=/route` as a habit; `path` and `state` are the highest-value queryable tags. Staging auto-promotes only when the repo is **bound** to the workspace. Check with `uploads github link --status` or `uploads staged` (binding field). If unbound, after the PR exists run: ```bash uploads attach --promote ``` ### Attach to an existing PR/issue ```bash uploads attach ./before.png ./after.png uploads attach ./shot.png --issue 45 --repo owner/name uploads put ./after.png --pr 123 --alt "Dashboard after" --width 700 uploads put --url https://cdn.example/shot.png --pr 123 uploads put --url http://localhost:4321/shot.png ``` `--pr` / `--issue` keys are stable and hash-free: `gh///pull|issues//`. They always overwrite in place. Other keys are strict (refuse overwrite unless `--replace`). ### Capture ```bash uploads screenshot http://localhost:4321/settings --out after.png --state after uploads screenshot https://example.com --via remote --pr 123 ``` `--via auto` prefers local Chrome, else remote server render (counts against the workspace upload budget). localhost targets are local-only. ### Annotate ```bash uploads screenshot http://localhost:3000 --via local --annotate ./callouts.json uploads annotate ./shot.png --spec ./callouts.json ``` See skill `annotate-screenshots` for the JSON spec. ### Manage ```bash uploads list --pr 123 uploads find path=/settings state=after uploads delete uploads usage uploads doctor uploads comment --pr 123 # re-sync managed comment without uploading ``` ## Hosted MCP (agents.uploads.sh) Use when the agent has **no local filesystem or git checkout**. Content is base64 or a public HTTPS `contentUrl` (the worker fetches it; filename comes from the URL path when omitted), not local paths. Workspace is inferred from the bearer / OAuth token. Important tools (not exhaustive): | Tool | Role | | --- | --- | | `put` | Upload `contentBase64` or `contentUrl`; with `pr`/`issue` + required `repo`, stable gh key + managed comment by default (`comment: false` to skip). With `branch` + `repo` (no pr/issue), stages under `gh/…/branch/…`. With `pr` + `branch`, also promotes that branch after upload | | `promote` | Copy branch-staged files into a PR (`repo` + `pr` + `branch` required); optional comment refresh (default on) | | `comment` | Refresh managed attachments comment without re-uploading | | `list` / `find_files` / `get_metadata` / `set_metadata` | Browse and tag objects | | `repo_link_status` | Binding tri-state: `self` \| `other` \| `none` (will staged files auto-attach?) | | `delete` / `usage` / galleries… | Lifecycle and collections | Hosted `put`/`promote` comment paths are **bot-only** (uploads-sh[bot]). No local `gh` fallback. Declines surface in the result (`not_installed`, `not_authorized`, `forbidden` with `fixUrl`) and never fail the upload itself. ### Why there is no hosted `attach` tool **Intentional.** Local stdio `attach` takes filesystem paths and can fall back to local `gh`. The Worker has neither. Hosted agents use `put` (PR attach or branch stage) and `promote` for the same outcomes. ### Why there is no hosted `staged` tool **Intentional.** Local `staged` defaults branch/repo from git. Hosted has no git context. Answer the same questions with: ```text list { prefix: "gh///branch//" } find_files { filters: { "gh.branch": "", "gh.repo": "/" } } repo_link_status { repo: "/" } ``` Local stdio MCP exposes a first-class `staged` tool mirroring the CLI. ## Skills | Skill | When | | --- | --- | | github-screenshots | Visual needs to land in a PR/issue/share link | | annotate-screenshots | Callouts, arrows, redaction on a capture | | uploads-cli | Exact flags, keys, MCP contracts, edge cases | Install: `npx skills add buildinternet/uploads` or `uploads install`. ## Endpoints - Web: https://uploads.sh - API: https://api.uploads.sh - Agents / MCP: https://agents.uploads.sh/mcp - Public storage: https://storage.uploads.sh - Embed (GitHub Camo-friendly): https://embed.uploads.sh - oEmbed: https://uploads.sh/oembed?url=&format=json ## Machine-readable discovery - Integration surfaces: https://uploads.sh/.well-known/integrations.json - API catalog: https://uploads.sh/.well-known/api-catalog - OpenAPI (summary): https://uploads.sh/.well-known/openapi.json (also /openapi.json) - MCP server card: https://uploads.sh/.well-known/mcp/server-card.json - Agent skills index: https://uploads.sh/.well-known/agent-skills/index.json - robots / sitemap: https://uploads.sh/robots.txt · https://uploads.sh/sitemap.xml Narrative API docs (not full OpenAPI): https://github.com/buildinternet/uploads/blob/main/docs/api.md ## Project instructions snippet Add to AGENTS.md / CLAUDE.md / Cursor rules in **consumer** repos: ```text ## Screenshots When a change is visually observable (UI, layout, styling), capture it as you go — don't wait until the PR is open. After each meaningful visual change: uploads put ./shot.png --state before # or --state after On a branch, that stages automatically — no --branch flag needed. Stage before/after pairs where it makes sense. No need to open the PR first — staged files are promoted into the PR's attachments comment automatically. ``` ## Cautions - Uploads are public until deleted. Private GitHub repos do not make media private. - `gh///pull|issues//` keys are predictable. - Prefer short compressed clips over large raw video on PRs. - Exit codes (CLI): `2` usage/token/file, `3` auth/policy, `4` network, `1` other. - Telemetry is anonymous command-name pings; opt out with `DO_NOT_TRACK=1` or `UPLOADS_TELEMETRY_DISABLED=1`. ## Docs map - Home: https://uploads.sh/ - Docs hub: https://uploads.sh/docs - Changelog: https://uploads.sh/changelog (Atom feed at https://uploads.sh/changelog.xml) - Attach & share: https://uploads.sh/docs/attach-pull-request-images - Galleries: https://uploads.sh/docs/galleries - GitHub App: https://uploads.sh/docs/github-app - Comment config (`.uploads.yml`): https://uploads.sh/docs/comment-config - Agents: https://uploads.sh/docs/agents - Reference: https://uploads.sh/docs/reference - Plans & limits: https://uploads.sh/docs/limits - Bring your own bucket: https://uploads.sh/docs/byo-bucket - Agent walkthrough: https://uploads.sh/github-screenshots - Source: https://github.com/buildinternet/uploads ## Legal - Terms: https://uploads.sh/terms - Privacy: https://uploads.sh/privacy - Operated by Build Internet: https://buildinternet.com ## Do not crawl as public product surface Enrollment invite pages (`/invite`) and the browser console (`/console`) are private operator surfaces. See https://uploads.sh/robots.txt.