Agent-readable docs

vcup Documentation

Reference for the vcup CLI and Vercel Blob-backed API: upload files, serve them inline at proxy URLs, delete by URL, and self-host on Vercel.

Pages

  1. OverviewWhat vcup exposes (CLI upload/delete, proxy file serving), runtime assumptions (Bun CLI, Vercel serverless + Blob), and the shortest path from install to a shareable link.
  2. InstallationInstall the global @maxleiter/vcup CLI with bun or npm, prerequisites, and how the published bin maps to cli.ts.
  3. QuickstartConfigure ~/.vcuprc or env vars, upload one file, verify the printed proxy URL opens inline, and optional --raw output.
  4. How vcup worksEnd-to-end flow: CLI streams to POST /api/upload, Vercel Blob put with random suffix, proxy slug served via /f rewrite to api/f, and optional delete path.
  5. Proxy and raw URLsUpload response fields url vs raw, /f/:slug rewrite behavior, inline Content-Disposition, and when the CLI or delete flow needs the blob store URL.
  6. AuthenticationShared VCUP_TOKEN secret: Bearer checks on upload and delete, client token in ~/.vcuprc or VCUP_TOKEN env, and which routes are public.
  7. Deploy on VercelClone deploy button env wiring, Blob store creation, required server env vars, and post-deploy dashboard steps from README.
  8. Configure the CLI~/.vcuprc JSON schema, VCUP_API_URL and VCUP_TOKEN precedence, and matching client settings to a deployed instance.
  9. Custom domainPoint a subdomain at Vercel, set ~/.vcuprc url to the custom host, and how VCUP_BASE_URL affects upload response proxy URLs.
  10. Upload and delete filesFile and stdin uploads, progress streaming, --raw flag, vcup rm with proxy or raw URLs, and BLOB_STORE_URL requirement for proxy deletes.
  11. CLI referencevcup subcommands and flags, stdin behavior, default paste.txt filename, exit codes, help text, and Authorization/X-Filename headers sent to the API.
  12. Configuration reference~/.vcuprc keys, client env vars, server env vars, VCUP_BASE_URL override, and validation errors returned by handlers.

Complete Markdown

# vcup Documentation

> Reference for the vcup CLI and Vercel Blob-backed API: upload files, serve them inline at proxy URLs, delete by URL, and self-host on Vercel.

## Context Links

- [Agent index](https://grok-wiki.com/public/docs/maxleiter-vcup-05c16ae77ebb/llms.txt)
- [Human interactive docs](https://grok-wiki.com/public/docs/maxleiter-vcup-05c16ae77ebb)
- [GitHub repository](https://github.com/MaxLeiter/vcup)

## Repository Metadata

- Repository: MaxLeiter/vcup

- Generated: 2026-06-03T07:41:10.700Z
- Updated: 2026-06-03T07:50:37.793Z
- Runtime: Grok CLI
- Format: Documentation
- Pages: 16

## Page Index

- 01. [Overview](https://grok-wiki.com/public/docs/maxleiter-vcup-05c16ae77ebb/pages/01-overview.md) - What vcup exposes (CLI upload/delete, proxy file serving), runtime assumptions (Bun CLI, Vercel serverless + Blob), and the shortest path from install to a shareable link.
- 02. [Installation](https://grok-wiki.com/public/docs/maxleiter-vcup-05c16ae77ebb/pages/02-installation.md) - Install the global @maxleiter/vcup CLI with bun or npm, prerequisites, and how the published bin maps to cli.ts.
- 03. [Quickstart](https://grok-wiki.com/public/docs/maxleiter-vcup-05c16ae77ebb/pages/03-quickstart.md) - Configure ~/.vcuprc or env vars, upload one file, verify the printed proxy URL opens inline, and optional --raw output.
- 04. [How vcup works](https://grok-wiki.com/public/docs/maxleiter-vcup-05c16ae77ebb/pages/04-how-vcup-works.md) - End-to-end flow: CLI streams to POST /api/upload, Vercel Blob put with random suffix, proxy slug served via /f rewrite to api/f, and optional delete path.
- 05. [Proxy and raw URLs](https://grok-wiki.com/public/docs/maxleiter-vcup-05c16ae77ebb/pages/05-proxy-and-raw-urls.md) - Upload response fields url vs raw, /f/:slug rewrite behavior, inline Content-Disposition, and when the CLI or delete flow needs the blob store URL.
- 06. [Authentication](https://grok-wiki.com/public/docs/maxleiter-vcup-05c16ae77ebb/pages/06-authentication.md) - Shared VCUP_TOKEN secret: Bearer checks on upload and delete, client token in ~/.vcuprc or VCUP_TOKEN env, and which routes are public.
- 07. [Deploy on Vercel](https://grok-wiki.com/public/docs/maxleiter-vcup-05c16ae77ebb/pages/07-deploy-on-vercel.md) - Clone deploy button env wiring, Blob store creation, required server env vars, and post-deploy dashboard steps from README.
- 08. [Configure the CLI](https://grok-wiki.com/public/docs/maxleiter-vcup-05c16ae77ebb/pages/08-configure-the-cli.md) - ~/.vcuprc JSON schema, VCUP_API_URL and VCUP_TOKEN precedence, and matching client settings to a deployed instance.
- 09. [Custom domain](https://grok-wiki.com/public/docs/maxleiter-vcup-05c16ae77ebb/pages/09-custom-domain.md) - Point a subdomain at Vercel, set ~/.vcuprc url to the custom host, and how VCUP_BASE_URL affects upload response proxy URLs.
- 10. [Upload and delete files](https://grok-wiki.com/public/docs/maxleiter-vcup-05c16ae77ebb/pages/10-upload-and-delete-files.md) - File and stdin uploads, progress streaming, --raw flag, vcup rm with proxy or raw URLs, and BLOB_STORE_URL requirement for proxy deletes.
- 11. [CLI reference](https://grok-wiki.com/public/docs/maxleiter-vcup-05c16ae77ebb/pages/11-cli-reference.md) - vcup subcommands and flags, stdin behavior, default paste.txt filename, exit codes, help text, and Authorization/X-Filename headers sent to the API.
- 12. [Configuration reference](https://grok-wiki.com/public/docs/maxleiter-vcup-05c16ae77ebb/pages/12-configuration-reference.md) - ~/.vcuprc keys, client env vars, server env vars, VCUP_BASE_URL override, and validation errors returned by handlers.
- 13. [API reference](https://grok-wiki.com/public/docs/maxleiter-vcup-05c16ae77ebb/pages/13-api-reference.md) - POST /api/upload, DELETE /api/delete, and GET /f/:slug (via rewrite): methods, headers, status codes, JSON response shape, and streaming behavior.
- 14. [Environment variables](https://grok-wiki.com/public/docs/maxleiter-vcup-05c16ae77ebb/pages/14-environment-variables.md) - Server: BLOB_READ_WRITE_TOKEN, BLOB_STORE_URL, VCUP_TOKEN, VCUP_BASE_URL. Client: VCUP_API_URL, VCUP_TOKEN, optional BLOB_STORE_URL for rm.
- 15. [Troubleshooting](https://grok-wiki.com/public/docs/maxleiter-vcup-05c16ae77ebb/pages/15-troubleshooting.md) - Source-backed failure modes: missing config, 401/400/405/500/502 responses, directory upload rejection, proxy delete without BLOB_STORE_URL, and upstream fetch errors.
- 16. [Local development](https://grok-wiki.com/public/docs/maxleiter-vcup-05c16ae77ebb/pages/16-local-development.md) - Run vercel dev via package.json script, local .env from .env.example, and exercising upload, /f proxy, and delete against a dev deployment.

## Source File Index

- `.env.example`
- `api/delete.ts`
- `api/f.ts`
- `api/upload.ts`
- `cli.ts`
- `package.json`
- `README.md`
- `vercel.json`

---

## 01. Overview

> What vcup exposes (CLI upload/delete, proxy file serving), runtime assumptions (Bun CLI, Vercel serverless + Blob), and the shortest path from install to a shareable link.

- Page Markdown: https://grok-wiki.com/public/docs/maxleiter-vcup-05c16ae77ebb/pages/01-overview.md
- Generated: 2026-06-03T07:38:08.516Z

### Source Files

- `README.md`
- `package.json`
- `cli.ts`
- `vercel.json`
- `api/upload.ts`
- `api/f.ts`

---
title: "Overview"
description: "What vcup exposes (CLI upload/delete, proxy file serving), runtime assumptions (Bun CLI, Vercel serverless + Blob), and the shortest path from install to a shareable link."
---

**vcup** (`@maxleiter/vcup`) is a Bun-driven CLI plus three Vercel serverless handlers that store files in Vercel Blob, return a proxy URL under `/f/:slug`, and stream those objects back with inline `Content-Disposition` for browser viewing. Upload and delete require a shared `VCUP_TOKEN`; the proxy route is unauthenticated.

## What the project exposes

| Surface | Entry | Role |
| --- | --- | --- |
| CLI | `vcup` → `cli.ts` | Upload files or stdin; optional `--raw`; delete via `vcup rm` |
| Upload API | `POST /api/upload` | Bearer auth, streams body to `@vercel/blob` `put`, returns JSON `url` + `raw` |
| Proxy API | `GET /f/:slug` → `/api/f` | Public fetch from `BLOB_STORE_URL`, MIME sniff, inline serve |
| Delete API | `DELETE /api/delete` | Bearer auth, `del` on blob URL from `X-Blob-Url` |

The published npm package wires the binary directly to the TypeScript entry:

```json
"bin": { "vcup": "./cli.ts" }
```

The CLI shebang (`#!/usr/bin/env bun`) and streaming upload (`duplex: "half"`) assume **Bun** at runtime, not Node for the client.

## Runtime stack

```mermaid
flowchart TB
  subgraph client["Client machine"]
    CLI["cli.ts / vcup"]
    RC["~/.vcuprc or VCUP_API_URL + VCUP_TOKEN"]
    CLI --> RC
  end

  subgraph vercel["Vercel deployment"]
    RW["vercel.json rewrite /f/:slug* → /api/f"]
    UP["api/upload.ts"]
    F["api/f.ts"]
    DEL["api/delete.ts"]
    RW --> F
  end

  subgraph storage["Vercel Blob"]
    BLOB["public blobs + random suffix"]
  end

  CLI -->|"POST + Bearer + X-Filename"| UP
  CLI -->|"DELETE + Bearer + X-Blob-Url"| DEL
  Browser["Browser / curl"] -->|"GET /f/..."| RW
  UP --> BLOB
  DEL --> BLOB
  F -->|"fetch BLOB_STORE_URL/slug"| BLOB
```

| Layer | Technology | Notes |
| --- | --- | --- |
| CLI | Bun | Config from env or `~/.vcuprc`; progress bar on stderr |
| API runtime | Vercel serverless | Node-style `IncomingMessage` / `ServerResponse` handlers |
| Object store | `@vercel/blob` | `put` with `access: "public"` and `addRandomSuffix: true`; `del` on delete |
| Routing | `vercel.json` | Rewrites `/f/:slug*` to `/api/f` |
| Local dev | `vercel dev` | `package.json` script `"dev": "vercel dev"` |

Server handlers disable the default body parser (`export const config = { api: { bodyParser: false } }`) so upload can stream the request body into Blob.

## Upload response shape

After a successful upload, the API returns JSON with two URLs:

<ResponseField name="url" type="string">
Proxy URL: `{baseUrl}/f/{slug}` where `slug` is the blob pathname (random suffix included). `baseUrl` is `VCUP_BASE_URL` or derived from `x-forwarded-proto` and `host`.
</ResponseField>

<ResponseField name="raw" type="string">
Direct Vercel Blob public URL returned by `put`.
</ResponseField>

The CLI prints `url` by default, or `raw` when `--raw` is passed.

## Proxy behavior (public)

`vercel.json` maps browser-facing paths to the proxy handler:

```json
{ "source": "/f/:slug*", "destination": "/api/f" }
```

`api/f.ts` rebuilds `blobUrl = ${BLOB_STORE_URL}/${slug}`, fetches upstream, sets `Content-Type` via `mime-types`, forces `Content-Disposition: inline`, and streams the body. No `VCUP_TOKEN` check on this route.

<Warning>
`BLOB_STORE_URL` must be set on the deployment or proxy requests return **500** with `BLOB_STORE_URL not configured`.
</Warning>

## Authentication boundary

| Route | Auth |
| --- | --- |
| `POST /api/upload` | `Authorization: Bearer {VCUP_TOKEN}` must match server `VCUP_TOKEN` |
| `DELETE /api/delete` | Same Bearer check |
| `GET /f/:slug` (via `/api/f`) | None |

Client token comes from `VCUP_TOKEN` / `~/.vcuprc` `token`, or `VCUP_API_URL` / `~/.vcuprc` `url` for the API host.

## Shortest path: install → shareable link

<Steps>
<Step title="Deploy or use an existing instance">
Fork/deploy via the README Vercel button (provisions Blob store env vars). Set `VCUP_TOKEN`, `BLOB_STORE_URL`, and `BLOB_READ_WRITE_TOKEN` on the project.
</Step>

<Step title="Install the CLI">
<CodeGroup>
```bash title="bun"
bun install -g @maxleiter/vcup
```
```bash title="npm"
npm install -g @maxleiter/vcup
```
</CodeGroup>
</Step>

<Step title="Point the CLI at your deployment">
Create `~/.vcuprc`:

```json
{
  "url": "https://your-vcup-deployment.vercel.app",
  "token": "your-vcup-token"
}
```

Or export `VCUP_API_URL` and `VCUP_TOKEN` (both required if using env-only config).
</Step>

<Step title="Upload and copy the printed URL">
```bash
vcup screenshot.png
```

On success the CLI prints the proxy URL (e.g. `https://your-deployment.vercel.app/f/...`). Open it in a browser to verify inline rendering.
</Step>
</Steps>

Stdin uploads work the same way without a file argument; the default filename is `paste.txt`:

```bash
echo "hello world" | vcup
```

## CLI commands at a glance

| Command | Behavior |
| --- | --- |
| `vcup <file>` | Upload file; stderr progress bar; stdout proxy URL |
| `echo … \| vcup` | Upload stdin as `paste.txt` |
| `vcup --raw <file>` | Print `raw` blob URL instead of proxy `url` |
| `vcup rm <url>` | `DELETE /api/delete` with proxy or raw URL |
| `vcup --help` | Usage and config hint |

Delete via proxy URL requires `BLOB_STORE_URL` in the **client** environment to resolve `/f/` slugs to a blob URL; otherwise pass the raw URL from upload.

## Server environment (deployment)

| Variable | Used by |
| --- | --- |
| `BLOB_READ_WRITE_TOKEN` | Vercel Blob SDK (implicit via platform) |
| `BLOB_STORE_URL` | `api/f.ts` upstream fetch; CLI proxy delete resolution |
| `VCUP_TOKEN` | `api/upload.ts`, `api/delete.ts` |
| `VCUP_BASE_URL` | Optional override for proxy URLs in upload JSON |

See `.env.example` for the minimal server template.

## Repository layout

:::files
vcup/
├── cli.ts          # Bun CLI (upload, rm, config)
├── package.json    # @maxleiter/vcup, bin → cli.ts
├── vercel.json     # /f → /api/f rewrite
└── api/
    ├── upload.ts   # POST, Blob put, JSON { url, raw }
    ├── f.ts        # GET proxy, inline stream
    └── delete.ts   # DELETE, Blob del
:::

<Info>
There is no separate web UI—sharing is entirely CLI upload plus HTTP GET on the proxy path.
</Info>

## Related pages

<CardGroup>
<Card title="Installation" href="/installation">
Install `@maxleiter/vcup` globally and how the `vcup` bin maps to `cli.ts`.
</Card>
<Card title="Quickstart" href="/quickstart">
Configure `~/.vcuprc`, upload one file, and verify the proxy URL.
</Card>
<Card title="How it works" href="/how-it-works">
End-to-end flow from CLI stream through Blob to `/f` proxy.
</Card>
<Card title="Deploy on Vercel" href="/deploy-vercel">
Clone deploy button, Blob store, and required server env vars.
</Card>
<Card title="Proxy and raw URLs" href="/proxy-and-raw-urls">
`url` vs `raw`, rewrite behavior, and inline disposition.
</Card>
<Card title="Authentication" href="/authentication">
Shared `VCUP_TOKEN` and which routes are public.
</Card>
</CardGroup>

---

## 02. Installation

> Install the global @maxleiter/vcup CLI with bun or npm, prerequisites, and how the published bin maps to cli.ts.

- Page Markdown: https://grok-wiki.com/public/docs/maxleiter-vcup-05c16ae77ebb/pages/02-installation.md
- Generated: 2026-06-03T07:38:25.504Z

### Source Files

- `package.json`
- `README.md`
- `cli.ts`

---
title: "Installation"
description: "Install the global @maxleiter/vcup CLI with bun or npm, prerequisites, and how the published bin maps to cli.ts."
---

The npm package `@maxleiter/vcup` (version `0.1.0`) publishes a single global command, `vcup`, whose `package.json` `bin` entry points at `cli.ts`. That file is executed by Bun via `#!/usr/bin/env bun`; the CLI talks to a separately deployed Vercel API (`POST /api/upload`, `DELETE /api/delete`) and does not start the server or Blob store on install.

## Prerequisites

| Requirement | Why it matters |
| --- | --- |
| **Bun on `PATH`** | `cli.ts` starts with `#!/usr/bin/env bun`; the global shim invokes that interpreter. |
| **A deployed vcup API** | Upload and delete call `config.url` (from `~/.vcuprc` or `VCUP_API_URL`). Install only adds the client; deploy your own instance or use an existing deployment. |
| **Matching `VCUP_TOKEN`** | Server handlers compare `Authorization: Bearer …` to `process.env.VCUP_TOKEN`; the client must use the same secret after configuration. |

<Note>
Global install does not create `~/.vcuprc`, set environment variables, or provision Vercel Blob. Those steps come after install; see [Configure the CLI](/configure-cli) and [Deploy on Vercel](/deploy-vercel).
</Note>

## Install the CLI globally

<Tabs>
<Tab title="Bun">
```bash
bun install -g @maxleiter/vcup
```
</Tab>
<Tab title="npm">
```bash
npm install -g @maxleiter/vcup
```
</Tab>
</Tabs>

Both commands install the scoped package from the registry and place a `vcup` executable on your global binary path. The repository documents these two installers in its README; there is no separate CLI-only package name.

## How `bin` maps to `cli.ts`

`package.json` declares the executable mapping:

```json
"bin": {
  "vcup": "./cli.ts"
}
```

After a global install, package managers link `vcup` to the published `cli.ts` in the package root. Invoking `vcup` runs that file; the shebang delegates execution to Bun:

```text
  @maxleiter/vcup (npm package)
  ├── package.json  →  "bin": { "vcup": "./cli.ts" }
  ├── cli.ts        →  #!/usr/bin/env bun  (upload / rm / --help)
  └── api/          →  Vercel serverless handlers (not started by the bin)
```

<Info>
The same tarball contains `api/upload.ts`, `api/delete.ts`, and `api/f.ts` plus server dependencies (`@vercel/blob`, `mime-types`). Those are used when the repo is deployed to Vercel, not when you run the global `vcup` command. The CLI uses Node/Bun built-ins (`fs`, `path`, `os`) and `fetch` only.
</Info>

## What gets installed

| Artifact | Role |
| --- | --- |
| `vcup` on `PATH` | Global command linked to `cli.ts` |
| `cli.ts` | Client: upload streaming, `vcup rm`, `--raw`, `--help` |
| `api/*.ts` | Server routes; inactive until deployed with `vercel dev` or Vercel |
| Dependencies | `@vercel/blob`, `mime-types` — required by API handlers, not imported by `cli.ts` |

Package metadata at publish time: name `@maxleiter/vcup`, version `0.1.0`, description *Simple file sharing via Vercel Blob*. The only `scripts` entry in `package.json` is `"dev": "vercel dev"` for local server work, not for end-user CLI setup.

## Verify the installation

<Steps>
<Step title="Confirm the command is on PATH">
```bash
which vcup
vcup --help
```
</Step>
<Step title="Expect help without API config">
`--help` (or no args with a TTY and no stdin) prints usage and exits before `loadConfig()` runs, so a fresh install can be checked without `~/.vcuprc` or env vars.
</Step>
<Step title="Configure before uploading">
Upload and delete require `VCUP_API_URL` + `VCUP_TOKEN`, or a `~/.vcuprc` with `url` and `token`. Without them, the CLI prints a sample JSON config and exits with code `1`.
</Step>
</Steps>

<RequestExample>
```bash
$ vcup --help
vcup — simple file sharing via Vercel Blob

Usage:
  vcup <file>              Upload a file
  echo "hi" | vcup         Upload from stdin
  vcup rm <url>            Delete a file by its proxy or raw URL
  vcup --raw <file>        Print raw blob URL instead of proxy URL
  vcup --help              Show this help
```
</RequestExample>

## Install vs deploy

```mermaid
flowchart LR
  subgraph client["Your machine"]
    CLI["vcup\n(cli.ts via Bun)"]
  end
  subgraph npm_pkg["@maxleiter/vcup package"]
    BIN["bin → cli.ts"]
    API["api/* handlers"]
  end
  subgraph vercel["Vercel deployment"]
    UP["POST /api/upload"]
    DEL["DELETE /api/delete"]
    PROXY["GET /f/:slug → api/f"]
    BLOB["Vercel Blob store"]
  end
  BIN --> CLI
  CLI -->|Bearer + X-Filename| UP
  CLI -->|Bearer + X-Blob-Url| DEL
  API --> UP
  API --> DEL
  API --> PROXY
  UP --> BLOB
  PROXY --> BLOB
```

- **Global install** — client only (`vcup`).
- **Vercel deploy** — clone/deploy the repo (or your fork), set `BLOB_READ_WRITE_TOKEN`, `BLOB_STORE_URL`, and `VCUP_TOKEN` per `.env.example`, then point the CLI at that deployment’s base URL.

<Warning>
If `vcup` fails with “env: bun: No such file or directory” (or similar), install Bun and ensure `bun` is on `PATH`. The published bin does not bundle a Node-compatible prebuilt JavaScript entry.
</Warning>

## Optional: run from a git checkout

Contributors can use the repo directly instead of a global registry install: clone the repository, install dependencies with Bun, and run `bun cli.ts` or link locally. Production CLI usage documented in the README is `bun install -g` / `npm install -g @maxleiter/vcup`. Local server iteration uses `npm run dev` (`vercel dev`); see [Local development](/local-development).

## Related pages

<CardGroup>
<Card title="Quickstart" href="/quickstart">
Configure `~/.vcuprc`, upload one file, and confirm the proxy URL.
</Card>
<Card title="Configure the CLI" href="/configure-cli">
`~/.vcuprc` schema and env precedence for `url` / `token`.
</Card>
<Card title="Deploy on Vercel" href="/deploy-vercel">
Blob store, env vars, and post-deploy steps for the API your CLI calls.
</Card>
<Card title="Overview" href="/overview">
End-to-end surfaces: CLI, proxy URLs, and runtime assumptions.
</Card>
</CardGroup>

---

## 03. Quickstart

> Configure ~/.vcuprc or env vars, upload one file, verify the printed proxy URL opens inline, and optional --raw output.

- Page Markdown: https://grok-wiki.com/public/docs/maxleiter-vcup-05c16ae77ebb/pages/03-quickstart.md
- Generated: 2026-06-03T07:38:08.749Z

### Source Files

- `README.md`
- `cli.ts`
- `api/upload.ts`
- `api/f.ts`

---
title: "Quickstart"
description: "Configure ~/.vcuprc or env vars, upload one file, verify the printed proxy URL opens inline, and optional --raw output."
---

The `vcup` CLI in `cli.ts` loads `VCUP_API_URL` and `VCUP_TOKEN` (or `~/.vcuprc`), streams a file to `POST /api/upload` with `Authorization: Bearer` and `X-Filename`, and prints the JSON `url` field—a proxy link under `/f/:slug` that `vercel.json` rewrites to `api/f.ts` for inline browser display. Use `--raw` to print the Vercel Blob URL from the `raw` field instead.

## Prerequisites

| Requirement | Notes |
| --- | --- |
| Deployed vcup instance | Vercel project with Blob store, `VCUP_TOKEN`, `BLOB_STORE_URL`, and `BLOB_READ_WRITE_TOKEN` set on the server. See [Deploy on Vercel](/deploy-vercel). |
| Matching client secret | The `token` in `~/.vcuprc` (or `VCUP_TOKEN` env) must equal the server `VCUP_TOKEN`. |
| CLI runtime | Global install uses Bun (`#!/usr/bin/env bun` in `cli.ts`). |

<Note>
This quickstart assumes a deployment already exists. If you need to create one first, follow [Deploy on Vercel](/deploy-vercel) before configuring the CLI.
</Note>

## Install the CLI

<Tabs>
  <Tab title="bun">
    ```bash
    bun install -g @maxleiter/vcup
    ```
  </Tab>
  <Tab title="npm">
    ```bash
    npm install -g @maxleiter/vcup
    ```
  </Tab>
</Tabs>

The published `vcup` bin maps to `cli.ts` (`package.json` → `"bin": { "vcup": "./cli.ts" }`).

## Configure the client

Point the CLI at your deployment API origin and shared upload secret.

<Steps>
  <Step title="Create ~/.vcuprc">
    ```json
    {
      "url": "https://your-vcup-deployment.vercel.app",
      "token": "your-vcup-token"
    }
    ```

    <ParamField body="url" type="string" required>
      Base URL of the vcup deployment (no trailing path). Used as `${url}/api/upload` and `${url}/api/delete`.
    </ParamField>

    <ParamField body="token" type="string" required>
      Shared secret; sent as `Authorization: Bearer &lt;token&gt;` on upload and delete.
    </ParamField>
  </Step>

  <Step title="Or set environment variables">
    <CodeGroup>
      ```bash title="Shell"
      export VCUP_API_URL="https://your-vcup-deployment.vercel.app"
      export VCUP_TOKEN="your-vcup-token"
      ```
    </CodeGroup>

    When **both** `VCUP_API_URL` and `VCUP_TOKEN` are set, `loadConfig()` returns them and does not read `~/.vcuprc`. If only one is set, the other can come from `~/.vcuprc` (`url || rc.url`, `token || rc.token`).
  </Step>

  <Step title="Confirm config loads">
    Run `vcup --help` with no file and a TTY stdin; missing config exits with a JSON example and status `1`.
  </Step>
</Steps>

<Warning>
Without `url` and `token`, upload fails before any network call: `Missing config. Set VCUP_API_URL and VCUP_TOKEN env vars, or create ~/.vcuprc`.
</Warning>

For precedence, partial overrides, and custom domains, see [Configure the CLI](/configure-cli) and [Custom domain](/custom-domain).

## Upload one file

```bash
vcup screenshot.png
```

Behavior:

- Resolves the path, rejects missing files and directories (`Cannot upload a directory`).
- Streams the body in 64KB chunks with a stderr progress bar.
- `POST` `${config.url}/api/upload` with headers `Authorization: Bearer ${token}` and `X-Filename: ${basename}`.

<ResponseExample>
```json
{
  "url": "https://your-vcup-deployment.vercel.app/f/myfile-abc123.png",
  "raw": "https://abc123.public.blob.vercel-storage.com/myfile-abc123.png"
}
```
</ResponseExample>

On success the CLI prints **one line** to stdout: `data.url` by default.

```mermaid
sequenceDiagram
  participant CLI as cli.ts
  participant API as api/upload.ts
  participant Blob as Vercel Blob

  CLI->>API: POST /api/upload<br/>Bearer + X-Filename + stream
  API->>API: Validate VCUP_TOKEN
  API->>Blob: put(filename, stream, public, random suffix)
  Blob-->>API: blob.url
  API->>API: slug = pathname; baseUrl from VCUP_BASE_URL or Host
  API-->>CLI: 200 { url: baseUrl/f/slug, raw: blob.url }
  CLI-->>CLI: stdout: url (or raw if --raw)
```

## Verify the proxy URL

1. Copy the printed `url` (path `/f/...`).
2. Open it in a browser or `curl -I` the URL.

The rewrite `{ "source": "/f/:slug*", "destination": "/api/f" }` routes to `api/f.ts`, which fetches `${BLOB_STORE_URL}/${slug}` and responds with:

| Header | Value |
| --- | --- |
| `Content-Type` | From `mime-types` lookup on the filename, else `application/octet-stream` |
| `Content-Disposition` | `inline` |
| `Cache-Control` | `public, max-age=31536000, immutable` |

<Check>
Inline display: images and PDFs should render in the browser tab rather than forcing a download, because `Content-Disposition` is `inline`.
</Check>

If the server lacks `BLOB_STORE_URL`, the proxy returns `500` with body `BLOB_STORE_URL not configured`. Missing slugs yield upstream `404` as `Not found`.

## Optional: print the raw blob URL

```bash
vcup --raw screenshot.png
```

The CLI still uploads the same way; stdout prints `data.raw` (the direct Vercel Blob URL from `put()`), not the `/f/` proxy link. Use this when integrating with tools that need the store URL, or for [Upload and delete](/upload-and-delete) flows that pass raw URLs to `vcup rm`.

<Info>
Server-side `VCUP_BASE_URL` overrides the host used in `url` in the upload JSON (for example a custom domain). It does not change what `--raw` prints.
</Info>

## Stdin upload (optional)

```bash
echo "hello world" | vcup
```

With no file argument and non-TTY stdin, the CLI reads all stdin, uses filename `paste.txt`, and prints the same `url` or `raw` line.

## Common failures (quickstart)

| Symptom | Likely cause |
| --- | --- |
| `Missing config` on first upload | No `~/.vcuprc` and incomplete env vars |
| `Upload failed: 401` | Client `token` ≠ server `VCUP_TOKEN` |
| `Upload failed: 400` | Missing `X-Filename` (should not happen from CLI) |
| Proxy `500 BLOB_STORE_URL not configured` | Server env not set after deploy |
| Proxy `502 Failed to fetch file` | Upstream fetch to blob store failed |

See [Troubleshooting](/troubleshooting) for full status codes and delete-path errors.

## Minimal command reference

| Command | Output |
| --- | --- |
| `vcup <file>` | Proxy URL (`url`) |
| `vcup --raw <file>` | Blob store URL (`raw`) |
| `echo "text" \| vcup` | Proxy URL for `paste.txt` |
| `vcup --help` | Usage and config hint |

Delete and progress-bar details: [Upload and delete](/upload-and-delete). Full flags and headers: [CLI reference](/cli-reference).

## Related pages

<CardGroup>
  <Card title="Installation" href="/installation">
    Global install with bun or npm and how the `vcup` bin maps to `cli.ts`.
  </Card>
  <Card title="Configure the CLI" href="/configure-cli">
    `~/.vcuprc` schema, env precedence, and matching a deployed instance.
  </Card>
  <Card title="Proxy and raw URLs" href="/proxy-and-raw-urls">
    `url` vs `raw`, `/f` rewrite, and inline `Content-Disposition`.
  </Card>
  <Card title="Deploy on Vercel" href="/deploy-vercel">
    Blob store, server env vars, and first deployment.
  </Card>
  <Card title="How vcup works" href="/how-it-works">
    End-to-end upload, proxy, and delete flow.
  </Card>
</CardGroup>

---

## 04. How vcup works

> End-to-end flow: CLI streams to POST /api/upload, Vercel Blob put with random suffix, proxy slug served via /f rewrite to api/f, and optional delete path.

- Page Markdown: https://grok-wiki.com/public/docs/maxleiter-vcup-05c16ae77ebb/pages/04-how-vcup-works.md
- Generated: 2026-06-03T07:38:20.847Z

### Source Files

- `api/upload.ts`
- `api/f.ts`
- `api/delete.ts`
- `vercel.json`
- `cli.ts`
- `package.json`

---
title: "How vcup works"
description: "End-to-end flow: CLI streams to POST /api/upload, Vercel Blob put with random suffix, proxy slug served via /f rewrite to api/f, and optional delete path."
---

vcup is a Bun CLI (`cli.ts`) plus three Vercel serverless handlers (`api/upload.ts`, `api/f.ts`, `api/delete.ts`) backed by `@vercel/blob`. Uploads stream into `put()` with `addRandomSuffix: true`; the API returns a short proxy URL under `/f/{slug}` and the direct blob URL. Public reads hit `/f/:slug*`, which `vercel.json` rewrites to `api/f.ts` for inline streaming from `BLOB_STORE_URL`. Deletes call `del()` with the raw blob URL after optional CLI-side slug resolution.

## System boundaries

```text
┌─────────────┐     POST /api/upload      ┌──────────────────┐
│  cli.ts     │ ─────────────────────────►│  api/upload.ts   │
│  (Bun)      │     Bearer + X-Filename   │  @vercel/blob put│
└─────────────┘                           └────────┬─────────┘
       │                                            │
       │ prints url or raw                          ▼
       │                                   Vercel Blob store
       ▼                                            │
  ~/.vcuprc / env                                   │ public object URL
                                                    │
┌─────────────┐     GET /f/:slug* (rewrite)         │
│  Browser    │ ─────────────────────────►┌────────▼─────────┐
└─────────────┘                           │  api/f.ts        │
                                          │  fetch(store/slug)│
                                          └──────────────────┘

┌─────────────┐     DELETE /api/delete    ┌──────────────────┐
│  vcup rm    │ ─────────────────────────►│  api/delete.ts   │
└─────────────┘     Bearer + X-Blob-Url   │  @vercel/blob del│
                                          └──────────────────┘
```

| Layer | Entry | Auth | Storage touch |
| --- | --- | --- | --- |
| CLI | `vcup <file>`, stdin, `vcup rm` | `Authorization: Bearer` from config | None (HTTP only) |
| Upload API | `POST /api/upload` | `VCUP_TOKEN` | `put(filename, stream)` |
| Proxy API | `GET /f/:slug*` → `/api/f` | None | `fetch(BLOB_STORE_URL/slug)` |
| Delete API | `DELETE /api/delete` | `VCUP_TOKEN` | `del(blobUrl)` |

## Upload path

The CLI loads `url` and `token` from `VCUP_API_URL` + `VCUP_TOKEN` (preferred) or `~/.vcuprc`, then builds the request body from a file path, stdin (`paste.txt` when piped), or exits with help when neither applies. Directories are rejected before any network call.

Upload uses a chunked `ReadableStream` with stderr progress (`progressStream` in `cli.ts`), `POST` to `{config.url}/api/upload`, and Bun’s `duplex: "half"` so the body streams without buffering the whole file in the handler path on the client.

:::endpoint POST /api/upload
Accepts the raw request body as the blob payload (`bodyParser: false`). Requires `Authorization: Bearer {VCUP_TOKEN}` and `X-Filename` with the object name used for `put()`.
:::

<ParamField header="Authorization" type="string" required>
Bearer token must equal server `VCUP_TOKEN`. Missing or mismatched token → `401 Unauthorized`.
</ParamField>

<ParamField header="X-Filename" type="string" required>
Filename passed to `@vercel/blob` `put()`. Missing → `400` with `Missing x-filename header`.
</ParamField>

The handler calls:

```ts
await put(filename, req, { access: "public", addRandomSuffix: true });
```

`addRandomSuffix: true` appends entropy to the stored pathname so repeated uploads of the same filename do not collide. The proxy slug is the blob URL pathname with the leading `/` removed:

```ts
const slug = new URL(blob.url).pathname.slice(1);
```

<ResponseField name="url" type="string">
Proxy link: `{baseUrl}/f/{slug}`. `baseUrl` is `VCUP_BASE_URL` when set, otherwise `x-forwarded-proto` + `host` from the upload request.
</ResponseField>

<ResponseField name="raw" type="string">
Direct Vercel Blob public URL returned by `put()`.
</ResponseField>

<ResponseExample>

```json
{
  "url": "https://your-deployment.vercel.app/f/paste-abc123.txt",
  "raw": "https://abc123.public.blob.vercel-storage.com/paste-abc123.txt"
}
```

</ResponseExample>

By default the CLI prints `url`. With `--raw`, it prints `raw` instead. Non-OK responses print status and body to stderr and exit `1`.

```mermaid
sequenceDiagram
  participant CLI as cli.ts
  participant API as api/upload.ts
  participant Blob as Vercel Blob

  CLI->>API: POST /api/upload<br/>Bearer, X-Filename, body stream
  API->>API: Validate method, token, filename
  API->>Blob: put(filename, req, addRandomSuffix)
  Blob-->>API: blob.url
  API->>API: slug = pathname of blob.url
  API-->>CLI: 200 { url, raw }
  CLI-->>CLI: stdout: url or raw per --raw
```

## Proxy serving (`/f`)

`vercel.json` maps browser-friendly paths to the proxy handler:

```json
{ "source": "/f/:slug*", "destination": "/api/f" }
```

`api/f.ts` accepts `GET` only. It parses `slug` from the path after `/f/`, requires `BLOB_STORE_URL`, and fetches `{BLOB_STORE_URL}/{slug}`. On success it sets:

| Header | Value |
| --- | --- |
| `Content-Type` | From `mime-types` `lookup()` on the filename segment, else `application/octet-stream` |
| `Content-Disposition` | `inline` (browser renders when possible) |
| `Cache-Control` | `public, max-age=31536000, immutable` |

The upstream body is streamed to the client with `ReadableStream` `getReader()` loops. Upstream non-OK statuses pass through; fetch failures return `502 Failed to fetch file`. Missing `BLOB_STORE_URL` returns `500 BLOB_STORE_URL not configured`.

<Note>
The proxy route is public: no `VCUP_TOKEN` check. Anyone with the `/f/{slug}` link can read the object while it exists in the store.
</Note>

```mermaid
sequenceDiagram
  participant Browser
  participant Vercel as Vercel rewrite
  participant F as api/f.ts
  participant Store as BLOB_STORE_URL

  Browser->>Vercel: GET /f/{slug}
  Vercel->>F: GET /api/f (same slug in path)
  F->>Store: fetch(store/slug)
  Store-->>F: object bytes
  F-->>Browser: 200 streamed, inline disposition
```

## Delete path

`vcup rm <url>` (alias `delete`) sends `DELETE` to `{config.url}/api/delete` with `Authorization: Bearer` and `X-Blob-Url` set to the blob store URL.

If the argument contains `/f/`, the CLI extracts the slug and, when `BLOB_STORE_URL` is set in the **client** environment, rewrites to `{BLOB_STORE_URL}/{slug}` before calling the API. Without client `BLOB_STORE_URL`, proxy URLs cannot be resolved and the CLI exits with an error instructing you to pass the raw URL or set `BLOB_STORE_URL`.

:::endpoint DELETE /api/delete
Authenticated delete by blob URL. Requires `X-Blob-Url` header with the full Vercel Blob URL (not the proxy `/f/` URL).
:::

<ParamField header="Authorization" type="string" required>
Same `VCUP_TOKEN` Bearer check as upload.
</ParamField>

<ParamField header="X-Blob-Url" type="string" required>
URL passed to `@vercel/blob` `del()`. Missing → `400 Missing x-blob-url header`.
</ParamField>

Success: `200` with body `Deleted`. The CLI prints `Deleted` on stdout.

```mermaid
sequenceDiagram
  participant CLI as cli.ts
  participant API as api/delete.ts
  participant Blob as Vercel Blob

  alt proxy URL argument
    CLI->>CLI: slug from /f/ + client BLOB_STORE_URL
  end
  CLI->>API: DELETE /api/delete<br/>Bearer, X-Blob-Url
  API->>Blob: del(url)
  API-->>CLI: 200 Deleted
```

## URL shapes and slug semantics

| Kind | Pattern | Produced by | Used for |
| --- | --- | --- | --- |
| Proxy | `{base}/f/{slug}` | Upload JSON `url` | Sharing, inline browser view |
| Raw | `https://….blob.vercel-storage.com/…` | Upload JSON `raw`, `--raw` flag | Direct blob access, delete target |
| Slug | Pathname after random suffix (may include `/`) | Derived from `blob.url` | Proxy path segment; must match store object key |

The slug is opaque: it mirrors the blob store pathname, including any random suffix segments `put()` adds.

## Runtime and dependencies

| Component | Runtime |
| --- | --- |
| CLI | Bun (`#!/usr/bin/env bun`), published as `@maxleiter/vcup` bin → `cli.ts` |
| API routes | Vercel serverless Node handlers (`IncomingMessage` / `ServerResponse`) |
| Blob SDK | `@vercel/blob` `put` / `del`; store credentials via `BLOB_READ_WRITE_TOKEN` (Vercel integration, not referenced in handler source) |

Local API development uses `npm run dev` → `vercel dev` per `package.json`.

## Configuration touchpoints

Server env vars drive handler behavior:

| Variable | Role in flow |
| --- | --- |
| `VCUP_TOKEN` | Upload/delete auth |
| `BLOB_STORE_URL` | Proxy fetch base in `api/f.ts`; optional CLI slug resolution for `rm` |
| `VCUP_BASE_URL` | Override proxy host in upload JSON `url` |
| `BLOB_READ_WRITE_TOKEN` | Required by Vercel Blob for `put`/`del` (wired at deploy) |

Client config (`~/.vcuprc` or `VCUP_API_URL` / `VCUP_TOKEN`) only needs the deployment origin and token for upload/delete. Client `BLOB_STORE_URL` is optional unless deleting via proxy URLs.

<Warning>
Upload and delete share one secret (`VCUP_TOKEN`). Rotate it on both the Vercel project and every CLI config that uploads or deletes.
</Warning>

## HTTP status summary

| Route | Method | Common errors |
| --- | --- | --- |
| `/api/upload` | POST | `405`, `401`, `400` (filename), upstream/blob failures |
| `/api/f` (via `/f/…`) | GET | `405`, `400` (slug), `500` (no store URL), upstream status, `502` |
| `/api/delete` | DELETE | `405`, `401`, `400` (blob URL) |

## Related pages

<CardGroup>
  <Card title="Proxy and raw URLs" href="/proxy-and-raw-urls">
    Compare `url` vs `raw`, rewrite behavior, and when the store URL is required.
  </Card>
  <Card title="Authentication" href="/authentication">
    Bearer checks, public `/f` reads, and token configuration.
  </Card>
  <Card title="Upload and delete files" href="/upload-and-delete">
    CLI commands, stdin uploads, `--raw`, and `vcup rm` resolution rules.
  </Card>
  <Card title="API reference" href="/api-reference">
    Per-route headers, status codes, and response shapes.
  </Card>
  <Card title="Environment variables" href="/environment-variables">
    Server and client variables for the full lifecycle.
  </Card>
</CardGroup>

---

## 05. Proxy and raw URLs

> Upload response fields url vs raw, /f/:slug rewrite behavior, inline Content-Disposition, and when the CLI or delete flow needs the blob store URL.

- Page Markdown: https://grok-wiki.com/public/docs/maxleiter-vcup-05c16ae77ebb/pages/05-proxy-and-raw-urls.md
- Generated: 2026-06-03T07:38:59.004Z

### Source Files

- `api/upload.ts`
- `api/f.ts`
- `vercel.json`
- `cli.ts`

---
title: "Proxy and raw URLs"
description: "Upload response fields url vs raw, /f/:slug rewrite behavior, inline Content-Disposition, and when the CLI or delete flow needs the blob store URL."
---

Every successful `POST /api/upload` returns two URLs: `url`, a deployment-hosted proxy path under `/f/…`, and `raw`, the public Vercel Blob URL returned by `put()`. Browsers and share links typically use the proxy URL so `api/f` can set `Content-Disposition: inline` and infer `Content-Type` from the filename; automation, direct blob access, and delete without client-side slug resolution use `raw` or require `BLOB_STORE_URL` to map proxy slugs back to the store.

## Upload response shape

The upload handler stores the file with `addRandomSuffix: true`, then derives a slug from the blob pathname (leading `/` stripped). The JSON body always includes both links:

<ResponseField name="url" type="string">
  Proxy URL: `{baseUrl}/f/{slug}` where `baseUrl` is `VCUP_BASE_URL` when set, otherwise `x-forwarded-proto` (default `http`) plus `Host` from the upload request.
</ResponseField>

<ResponseField name="raw" type="string">
  Direct blob URL from `@vercel/blob` `put()` (public store URL including the random suffix in the path).
</ResponseField>

<RequestExample>
```http
POST /api/upload HTTP/1.1
Authorization: Bearer <VCUP_TOKEN>
X-Filename: screenshot.png

<request body stream>
```
</RequestExample>

<ResponseExample>
```json
{
  "url": "https://your-vcup.vercel.app/f/screenshot-abc123.png",
  "raw": "https://abc123.public.blob.vercel-storage.com/screenshot-abc123.png"
}
```
</ResponseExample>

<Note>
Example hostnames are illustrative. `raw` always matches your blob store’s public base URL; `url` follows whatever base the server computed for that upload.
</Note>

| Field | Host | Path pattern | Typical use |
| --- | --- | --- | --- |
| `url` | Your vcup deployment (or `VCUP_BASE_URL`) | `/f/{slug}` | Share in chat, open in browser with inline rendering |
| `raw` | `*.public.blob.vercel-storage.com` (store URL) | `/{slug}` | `vcup --raw`, scripts, delete without proxy resolution |

The slug in `url` is the blob pathname after upload (random suffix included), not a separate ID.

## Proxy route and rewrite

Vercel rewrites public `/f/*` requests to the file proxy handler:

```json
{ "source": "/f/:slug*", "destination": "/api/f" }
```

A GET to `https://<deployment>/f/myfile-xyz.png` runs `api/f.ts`, which strips the `/f/` prefix, rebuilds `${BLOB_STORE_URL}/${slug}`, fetches upstream, and streams the body to the client.

```mermaid
sequenceDiagram
  participant Browser
  participant Vercel as Vercel rewrite
  participant F as api/f
  participant Blob as BLOB_STORE_URL

  Browser->>Vercel: GET /f/{slug}
  Vercel->>F: GET /api/f (pathname still /f/{slug})
  F->>F: slug = pathname without /f/
  F->>Blob: fetch {BLOB_STORE_URL}/{slug}
  Blob-->>F: 200 body stream
  F-->>Browser: 200 inline + Content-Type
```

<Info>
`GET /f/:slug` is unauthenticated. Only upload and delete require `VCUP_TOKEN`.
</Info>

## Inline serving headers

The proxy handler sets response headers before streaming:

| Header | Value | Effect |
| --- | --- | --- |
| `Content-Type` | `mime-types` lookup on the last path segment of `slug`, else `application/octet-stream` | Browser picks a viewer (image, text, etc.) |
| `Content-Disposition` | `inline` | Prefer in-page display instead of forced download |
| `Cache-Control` | `public, max-age=31536000, immutable` | Long-lived cache for immutable blob objects |

Upstream non-OK status is passed through (body `Not found`). Fetch failures return `502` with `Failed to fetch file`. Missing server `BLOB_STORE_URL` returns `500` with `BLOB_STORE_URL not configured`.

Opening the `raw` URL in a browser hits Vercel Blob directly; disposition and type come from blob defaults, not vcup’s proxy layer.

## CLI output: default proxy vs `--raw`

After upload, the CLI prints one line from the JSON response:

- Default: `data.url` (proxy link)
- With `--raw`: `data.raw` (blob store link)

```bash
vcup photo.png          # prints proxy URL
vcup --raw photo.png    # prints raw blob URL
```

`VCUP_API_URL` / `~/.vcuprc` `url` must point at the API host used for upload; it does not change which field is printed—that is controlled only by `--raw`.

## When `BLOB_STORE_URL` is required

`BLOB_STORE_URL` is the public base of the blob store (for example `https://abc123.public.blob.vercel-storage.com`). It appears in three places:

| Surface | Role |
| --- | --- |
| Server (`api/f.ts`) | Reconstruct `fetch(`${BLOB_STORE_URL}/${slug}`)` for every proxy GET |
| Server deploy | Must be set in project env for `/f/*` to work |
| CLI (`vcup rm` with proxy URL) | Map `/f/{slug}` → `{BLOB_STORE_URL}/{slug}` before calling delete |

### Delete with proxy vs raw URLs

`vcup rm` accepts either URL form. Delete API always needs the full blob URL in `X-Blob-Url`:

<Steps>
<Step title="Pass a raw blob URL">
  The CLI sends `target` unchanged to `DELETE /api/delete` with `X-Blob-Url: <raw>`. No client `BLOB_STORE_URL` needed.
</Step>
<Step title="Pass a proxy URL containing /f/">
  The CLI extracts the slug after `/f/`, then requires `BLOB_STORE_URL` in the **client** environment to build `${storeUrl}/${slug}`. Without it, the CLI exits with an error asking for the raw URL or `BLOB_STORE_URL`.
</Step>
</Steps>

```bash
# Works if BLOB_STORE_URL is set in the shell (client-side)
export BLOB_STORE_URL=https://abc123.public.blob.vercel-storage.com
vcup rm https://your-vcup.vercel.app/f/file-xyz.png

# Always works — no slug resolution
vcup rm https://abc123.public.blob.vercel-storage.com/file-xyz.png
```

<Warning>
Proxy delete resolution uses **client** `BLOB_STORE_URL`, while proxy **serving** uses **server** `BLOB_STORE_URL`. Both should be the same store base URL; only the machine running `vcup rm` needs the var when deleting by proxy link.
</Warning>

## Custom base URL for proxy links

Set server `VCUP_BASE_URL` (for example `https://files.example.com`) so upload responses emit proxy URLs on that host instead of the request `Host`. The path remains `/f/{slug}`; rewrites still route to `api/f` on the deployment. Pair with a custom domain on Vercel and matching `~/.vcuprc` / `VCUP_API_URL` for uploads.

## Mental model

```text
  POST /api/upload
        │
        ├─► raw  ──► Vercel Blob (direct public URL)
        │
        └─► url  ──► {base}/f/{slug}
                      │
                      GET (rewrite) ──► api/f ──► fetch(BLOB_STORE_URL/slug)
                                              └─► inline + typed stream

  vcup rm(proxy) ──► needs client BLOB_STORE_URL ──► raw blob URL ──► DELETE /api/delete
  vcup rm(raw)     ──► X-Blob-Url as-is
```

<Check>
Use the proxy `url` when sharing links that should render inline in browsers. Use `raw` or `--raw` when integrating with blob-native tools, storing the canonical store location, or deleting without configuring client `BLOB_STORE_URL`.
</Check>

## Related pages

<CardGroup>
<Card title="How vcup works" href="/how-it-works">
  End-to-end upload, rewrite, proxy fetch, and delete flow.
</Card>
<Card title="Custom domain" href="/custom-domain">
  `VCUP_BASE_URL` and client `url` for proxy links on your own host.
</Card>
<Card title="Upload and delete" href="/upload-and-delete">
  CLI upload flags, `vcup rm`, and proxy delete prerequisites.
</Card>
<Card title="API reference" href="/api-reference">
  `POST /api/upload`, `GET /f/:slug`, and `DELETE /api/delete` contracts.
</Card>
<Card title="Environment variables" href="/environment-variables">
  `BLOB_STORE_URL`, `VCUP_BASE_URL`, and client vs server scope.
</Card>
<Card title="Troubleshooting" href="/troubleshooting">
  500/502 on proxy GET and proxy delete without `BLOB_STORE_URL`.
</Card>
</CardGroup>

---

## 06. Authentication

> Shared VCUP_TOKEN secret: Bearer checks on upload and delete, client token in ~/.vcuprc or VCUP_TOKEN env, and which routes are public.

- Page Markdown: https://grok-wiki.com/public/docs/maxleiter-vcup-05c16ae77ebb/pages/06-authentication.md
- Generated: 2026-06-03T07:39:03.072Z

### Source Files

- `api/upload.ts`
- `api/delete.ts`
- `cli.ts`
- `.env.example`
- `README.md`

---
title: "Authentication"
description: "Shared VCUP_TOKEN secret: Bearer checks on upload and delete, client token in ~/.vcuprc or VCUP_TOKEN env, and which routes are public."
---

vcup protects mutating API routes with a single shared secret: the server reads `VCUP_TOKEN` from the deployment environment, and `POST /api/upload` and `DELETE /api/delete` reject requests unless the `Authorization` header carries `Bearer <token>` with an exact match. The CLI loads the same value from `VCUP_TOKEN` or `~/.vcuprc` and sends it on every upload and delete. File serving at `/f/:slug` is unauthenticated.

## Shared secret model

| Role | Variable | Where set |
|------|----------|-----------|
| Server | `VCUP_TOKEN` | Vercel project env (see `.env.example`) |
| CLI | `token` in `~/.vcuprc` | User home directory |
| CLI | `VCUP_TOKEN` | Shell environment |

There is no per-user identity, token rotation API, or scoped permissions. Anyone who knows `VCUP_TOKEN` can upload and delete through the API. Anyone who has a proxy or raw blob URL can read the file without the secret.

<Warning>
Uploaded blobs are stored with `access: "public"` in Vercel Blob. Proxy URLs and raw blob URLs are readable without `VCUP_TOKEN`. Authentication gates writes and deletes, not read access to objects you have already shared.
</Warning>

## Protected routes

Both handlers use the same check before any business logic:

```ts
const token = req.headers.authorization?.replace("Bearer ", "");
if (!process.env.VCUP_TOKEN || token !== process.env.VCUP_TOKEN) {
  res.statusCode = 401;
  res.end("Unauthorized");
  return;
}
```

| Route | Method | Auth required | Handler |
|-------|--------|---------------|---------|
| `/api/upload` | `POST` | Yes — `Authorization: Bearer <VCUP_TOKEN>` | `api/upload.ts` |
| `/api/delete` | `DELETE` | Yes — `Authorization: Bearer <VCUP_TOKEN>` | `api/delete.ts` |
| `/f/:slug` (rewritten to `/api/f`) | `GET` | No | `api/f.ts` via `vercel.json` |

:::endpoint POST /api/upload
Requires a valid Bearer token and `X-Filename` before streaming the body to Vercel Blob.
:::

<ParamField header="Authorization" type="string" required>
`Bearer <VCUP_TOKEN>` — prefix is stripped with a single `replace("Bearer ", "")` on the header value.
</ParamField>

<ResponseField name="status" type="number">
`401` with body `Unauthorized` when the server env is unset, the header is missing, or the token does not match exactly.
</ResponseField>

:::endpoint DELETE /api/delete
Requires a valid Bearer token and `X-Blob-Url` before calling Vercel Blob `del`.
:::

<ParamField header="Authorization" type="string" required>
Same Bearer scheme as upload.
</ParamField>

<ResponseField name="status" type="number">
`401` with body `Unauthorized` under the same conditions as upload.
</ResponseField>

Other status codes on these routes (for example `405` for wrong method, `400` for missing headers) are returned only after authentication succeeds.

## Public routes

`vercel.json` rewrites `/f/:slug*` to `/api/f`. The proxy handler accepts `GET` only, does not read `Authorization`, and streams content from `BLOB_STORE_URL` plus the slug path.

| Surface | Auth | Notes |
|---------|------|-------|
| `GET /f/<slug>` | None | Inline `Content-Disposition`; long cache headers |
| Raw Vercel Blob URL (`raw` in upload JSON) | None | Public blob store URL returned at upload time |

<Info>
Knowing a slug or raw URL is sufficient to download a file. Treat shareable links as capability URLs, not as proof of identity.
</Info>

## CLI client credentials

`cli.ts` loads credentials in `loadConfig()` before upload or delete:

1. If **both** `VCUP_API_URL` and `VCUP_TOKEN` are set in the environment, use those values and skip the rc file.
2. Otherwise read `~/.vcuprc` if it exists and merge: env vars override individual `url` / `token` fields from JSON.
3. If configuration is still incomplete, print an error with an example rc object and exit with code `1`.

<Steps>
<Step title="Set the server secret">
On Vercel, set `VCUP_TOKEN` to a long random string (README: any random secret). The deploy button wires this variable from `.env.example`.
</Step>
<Step title="Mirror the secret on the client">
Create `~/.vcuprc` with the same `token`, or export `VCUP_TOKEN` (and `VCUP_API_URL` if you rely on env-only config).
</Step>
<Step title="Verify upload auth">
Run `vcup <file>`. The CLI sends `Authorization: Bearer ${config.token}` to `${config.url}/api/upload`.
</Step>
</Steps>

Example rc file:

```json
{
  "url": "https://your-vcup-deployment.vercel.app",
  "token": "your-vcup-token"
}
```

Upload and delete requests from the CLI:

```ts
headers: {
  Authorization: `Bearer ${config.token}`,
  "X-Filename": filename,  // upload only
}
// delete also sets "X-Blob-Url": blobUrl
```

<Note>
`VCUP_TOKEN` names the same secret on server and client. The server never reads `~/.vcuprc`; only the CLI does.
</Note>

## Request flow (upload)

```mermaid
sequenceDiagram
  participant CLI as cli.ts
  participant API as api/upload.ts
  participant Blob as Vercel Blob

  CLI->>CLI: loadConfig() → token
  CLI->>API: POST /api/upload<br/>Authorization: Bearer token<br/>X-Filename
  API->>API: token === process.env.VCUP_TOKEN?
  alt mismatch or missing server VCUP_TOKEN
    API-->>CLI: 401 Unauthorized
  else authorized
    API->>Blob: put(filename, body, public)
    API-->>CLI: 200 { url, raw }
  end
```

Delete follows the same Bearer gate, then requires `X-Blob-Url`.

## Failure modes

| Symptom | Typical cause |
|---------|----------------|
| `401 Unauthorized` on upload/delete | Wrong token, missing `Authorization`, or `VCUP_TOKEN` not set on the server |
| CLI exits before request | Missing `~/.vcuprc` and incomplete env (`VCUP_API_URL` / `VCUP_TOKEN`) |
| Upload works but link is world-readable | Expected — proxy and raw URLs are not behind Bearer auth |

<Tip>
For local or CI uploads, export both `VCUP_API_URL` and `VCUP_TOKEN` so the CLI does not depend on a home-directory rc file.
</Tip>

## Operational checklist

- Generate a strong random `VCUP_TOKEN` for production; do not commit it.
- Set the identical value in Vercel env and client config.
- Rotate by updating server env and all clients; there is no grace period in code.
- Restrict who receives proxy or raw URLs if read access must stay private (vcup does not add download auth).

## Related pages

<CardGroup>
<Card title="Configure the CLI" href="/configure-cli">
`~/.vcuprc` schema, env precedence, and matching client settings to your deployment.
</Card>
<Card title="Environment variables" href="/environment-variables">
Server `VCUP_TOKEN` and client `VCUP_API_URL` / `VCUP_TOKEN` reference.
</Card>
<Card title="API reference" href="/api-reference">
Upload, delete, and proxy endpoints with headers and status codes.
</Card>
<Card title="Troubleshooting" href="/troubleshooting">
401 responses, missing config, and other source-backed errors.
</Card>
</CardGroup>

---

## 07. Deploy on Vercel

> Clone deploy button env wiring, Blob store creation, required server env vars, and post-deploy dashboard steps from README.

- Page Markdown: https://grok-wiki.com/public/docs/maxleiter-vcup-05c16ae77ebb/pages/07-deploy-on-vercel.md
- Generated: 2026-06-03T07:39:24.378Z

### Source Files

- `README.md`
- `.env.example`
- `vercel.json`
- `package.json`
- `api/upload.ts`
- `api/f.ts`

---
title: "Deploy on Vercel"
description: "Clone deploy button env wiring, Blob store creation, required server env vars, and post-deploy dashboard steps from README."
---

vcup runs as a Vercel project with three serverless handlers under `api/` (`upload`, `delete`, `f`), a `vercel.json` rewrite that maps `/f/:slug*` to `api/f`, and Vercel Blob as the object store. The README **Deploy with Vercel** button clones the repo, provisions a Blob store, and prompts for the server environment variables the handlers and `@vercel/blob` expect.

## Runtime layout on Vercel

```mermaid
flowchart TB
  subgraph client["CLI (cli.ts)"]
    CLI["vcup / vcup rm"]
  end

  subgraph vercel["Vercel deployment"]
    RW["vercel.json rewrite /f/:slug* → /api/f"]
    UP["api/upload.ts POST"]
    DEL["api/delete.ts DELETE"]
    PROXY["api/f.ts GET"]
  end

  subgraph storage["Vercel Blob"]
    BLOB["public blobs + random suffix"]
  end

  CLI -->|"POST /api/upload Bearer + X-Filename"| UP
  CLI -->|"DELETE /api/delete Bearer + X-Blob-Url"| DEL
  CLI -->|"opens proxy URL"| RW
  RW --> PROXY
  UP -->|"put()"| BLOB
  DEL -->|"del()"| BLOB
  PROXY -->|"fetch BLOB_STORE_URL + slug"| BLOB
```

| Surface | Path / handler | Auth | Blob dependency |
| --- | --- | --- | --- |
| Upload | `POST /api/upload` → `api/upload.ts` | `VCUP_TOKEN` Bearer | `put()` via `@vercel/blob` |
| Delete | `DELETE /api/delete` → `api/delete.ts` | `VCUP_TOKEN` Bearer | `del()` via `@vercel/blob` |
| Inline proxy | `GET /f/:slug` → rewrite → `api/f.ts` | None (public) | `BLOB_STORE_URL` + slug fetch |

The dev entrypoint is `npm run dev` / `bun run dev`, which runs `vercel dev` from `package.json`.

## Deploy with Vercel button

The README ships a one-click deploy link. Decoded query parameters:

| Parameter | Value | Effect |
| --- | --- | --- |
| `repository-url` | `https://github.com/maxleiter/vcup` | Clones the vcup repo |
| `env` | `BLOB_READ_WRITE_TOKEN,BLOB_STORE_URL,VCUP_TOKEN` | Prompts for these three vars during setup |
| `envDescription` | `See .env.example for details` | Points operators at `.env.example` |
| `stores` | `[{"type":"blob"}]` | Provisions a Vercel Blob store on the new project |

Use the button from the README or open:

```
https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fmaxleiter%2Fvcup&env=BLOB_READ_WRITE_TOKEN,BLOB_STORE_URL,VCUP_TOKEN&envDescription=See%20.env.example%20for%20details&stores=%5B%7B%22type%22%3A%22blob%22%7D%5D
```

<Note>
The button pre-registers all three server variables. Values still need to be filled or corrected after the Blob store exists—especially `BLOB_STORE_URL` and a chosen `VCUP_TOKEN`.
</Note>

## Required server environment variables

`.env.example` documents the same three variables the deploy button requests:

```env
BLOB_READ_WRITE_TOKEN=
BLOB_STORE_URL=
VCUP_TOKEN=
```

| Variable | Required on server | Role in this repo |
| --- | --- | --- |
| `BLOB_READ_WRITE_TOKEN` | Yes | Used by `@vercel/blob` `put()` / `del()` on Vercel when the project is linked to Blob storage (not read directly in handler source) |
| `BLOB_STORE_URL` | Yes | Public store origin for `api/f.ts` proxy fetches (`${storeUrl}/${slug}`); returns **500** `BLOB_STORE_URL not configured` when missing |
| `VCUP_TOKEN` | Yes | Shared secret; upload and delete return **401** when unset or Bearer mismatch |
| `VCUP_BASE_URL` | No | Overrides the host used in upload JSON `url` (proxy link); defaults to `x-forwarded-proto` + `host` |

Example store URL shape from the README: `https://abc123.public.blob.vercel-storage.com`.

## Post-deploy dashboard steps

<Steps>
<Step title="Open Storage and copy Blob credentials">

In the Vercel project **Storage** tab, note `BLOB_READ_WRITE_TOKEN` and the store’s public URL after the Blob store is created (the deploy flow’s `stores` parameter provisions one).

</Step>
<Step title="Set VCUP_TOKEN">

In **Settings → Environment Variables**, set `VCUP_TOKEN` to any random secret string. The same value must be configured on every CLI that uploads or deletes.

</Step>
<Step title="Set BLOB_STORE_URL">

Set `BLOB_STORE_URL` to the store’s public URL (for example `https://abc123.public.blob.vercel-storage.com`). Without it, `GET /f/...` proxy requests fail with **500**, and the CLI cannot resolve proxy URLs for `vcup rm` unless you pass the raw blob URL.

</Step>
<Step title="Confirm BLOB_READ_WRITE_TOKEN">

Ensure `BLOB_READ_WRITE_TOKEN` is present for the linked Blob store (typically auto-populated when Storage is connected). Uploads and deletes depend on `@vercel/blob` with this token on the deployment.

</Step>
<Step title="Redeploy if needed">

After changing environment variables, trigger a redeploy so serverless functions pick up the new values.

</Step>
</Steps>

## Proxy rewrite

`vercel.json` defines a single rewrite:

```json
{
  "rewrites": [
    { "source": "/f/:slug*", "destination": "/api/f" }
  ]
}
```

Upload responses return a proxy URL shaped as `{baseUrl}/f/{slug}`, where `slug` is the pathname segment from the blob URL after `put(..., { addRandomSuffix: true })`. The proxy handler streams bytes from `BLOB_STORE_URL` with inline `Content-Disposition`.

## Point the CLI at the deployment

After the deployment URL is live (default `*.vercel.app` or a custom domain), configure clients to call that origin—not the Blob store URL.

<Tabs>
<Tab title="~/.vcuprc">

```json
{
  "url": "https://your-vcup-deployment.vercel.app",
  "token": "your-vcup-token"
}
```

`token` must match server `VCUP_TOKEN`. `url` is the deployment base used for `/api/upload` and `/api/delete`.

</Tab>
<Tab title="Environment variables">

```bash
export VCUP_API_URL="https://your-vcup-deployment.vercel.app"
export VCUP_TOKEN="your-vcup-token"
```

`VCUP_API_URL` and `VCUP_TOKEN` take precedence over `~/.vcuprc` when both are set.

</Tab>
</Tabs>

<Warning>
Client `VCUP_TOKEN` is the same secret as server `VCUP_TOKEN`. Client `VCUP_API_URL` is the deployment origin; do not point it at `BLOB_STORE_URL`.
</Warning>

## Optional: custom domain and VCUP_BASE_URL

For a custom host (README: `vercel domains add`, CNAME to `cname.vercel-dns.com`), set `~/.vcuprc` `url` (or `VCUP_API_URL`) to that host. If upload responses should emit proxy links on the custom host while requests still hit Vercel, set server `VCUP_BASE_URL` to that public origin; otherwise upload uses the incoming request host headers.

## Verify the deployment

<Steps>
<Step title="Upload a test file">

```bash
vcup /path/to/small-file.txt
```

Expect a printed URL like `https://<deployment>/f/<slug>`.

</Step>
<Step title="Open the proxy URL">

Open the printed URL in a browser. A working proxy returns **200** with inline content; missing `BLOB_STORE_URL` yields **500** `BLOB_STORE_URL not configured`.

</Step>
<Step title="Check auth">

Wrong or missing Bearer on upload returns **401** `Unauthorized` from `api/upload.ts`.

</Step>
</Steps>

## Failure modes tied to deploy config

| Symptom | Likely cause |
| --- | --- |
| Upload **401** | `VCUP_TOKEN` mismatch or unset on server vs CLI |
| Proxy **500** `BLOB_STORE_URL not configured` | `BLOB_STORE_URL` not set on the deployment |
| Proxy **502** `Failed to fetch file` | Wrong store URL or slug; upstream fetch error |
| `vcup rm` on proxy URL fails locally | CLI needs client `BLOB_STORE_URL` to resolve `/f/` slugs, or pass the raw blob URL |
| Proxy links use wrong host | Set `VCUP_BASE_URL` to the public deployment or custom domain |

## Related pages

<CardGroup>
<Card title="Environment variables" href="/environment-variables">
Server and client variable reference for Blob, auth, and API URL.
</Card>
<Card title="Configure the CLI" href="/configure-cli">
`~/.vcuprc` schema and env precedence after deploy.
</Card>
<Card title="Custom domain" href="/custom-domain">
Domain DNS, `VCUP_BASE_URL`, and client `url` alignment.
</Card>
<Card title="Local development" href="/local-development">
`vercel dev` and `.env` from `.env.example` before production deploy.
</Card>
<Card title="Quickstart" href="/quickstart">
First upload and proxy URL check against a live deployment.
</Card>
</CardGroup>

---

## 08. Configure the CLI

> ~/.vcuprc JSON schema, VCUP_API_URL and VCUP_TOKEN precedence, and matching client settings to a deployed instance.

- Page Markdown: https://grok-wiki.com/public/docs/maxleiter-vcup-05c16ae77ebb/pages/08-configure-the-cli.md
- Generated: 2026-06-03T07:39:24.679Z

### Source Files

- `cli.ts`
- `README.md`
- `package.json`

---
title: "Configure the CLI"
description: "~/.vcuprc JSON schema, VCUP_API_URL and VCUP_TOKEN precedence, and matching client settings to a deployed instance."
---

The `vcup` binary resolves API base URL and bearer token once per invocation via `loadConfig()` in `cli.ts`: it reads `VCUP_API_URL` and `VCUP_TOKEN`, optionally merges them with `~/.vcuprc`, and uses the result for `POST ${url}/api/upload` and `DELETE ${url}/api/delete`.

## Configuration surface

| Surface | Path or name | Used for |
| --- | --- | --- |
| Config file | `~/.vcuprc` | Default `url` and `token` when env vars are absent or partial |
| Client env | `VCUP_API_URL` | API deployment origin (overrides or replaces `url` in rc) |
| Client env | `VCUP_TOKEN` | Shared secret sent as `Authorization: Bearer` (overrides or replaces `token` in rc) |
| Client env (delete only) | `BLOB_STORE_URL` | Resolving proxy `/f/…` URLs to raw blob URLs for `vcup rm` |

Server-side variables (`VCUP_TOKEN` on Vercel, `VCUP_BASE_URL`, blob store settings) are not read by `loadConfig()`; they must match what you deploy. See [Deploy on Vercel](/deploy-vercel) and [Environment variables](/environment-variables).

## `~/.vcuprc` schema

The CLI expects a single JSON object with two string fields. There is no schema validator beyond `JSON.parse` and property access.

<ParamField body="url" type="string" required>
Deployment origin for API calls. Becomes the prefix in `${url}/api/upload` and `${url}/api/delete`. Use the Vercel deployment URL or your custom domain root (no path suffix).
</ParamField>

<ParamField body="token" type="string" required>
Must equal the `VCUP_TOKEN` value configured on the server. Sent on every authenticated API request.
</ParamField>

<RequestExample>

```json title="~/.vcuprc"
{
  "url": "https://your-vcup-deployment.vercel.app",
  "token": "your-vcup-token"
}
```

</RequestExample>

<Warning>
Invalid JSON in `~/.vcuprc` throws at parse time and aborts the process. Missing `url` or `token` after merging env and file yields requests with `undefined` in the URL or header—configure both values explicitly.
</Warning>

## Resolution order

`loadConfig()` applies a two-stage rule set:

1. **Full env shortcut** — If both `VCUP_API_URL` and `VCUP_TOKEN` are set, return them immediately. `~/.vcuprc` is not read.
2. **File with per-field env override** — If `~/.vcuprc` exists, parse it and set `url` to `process.env.VCUP_API_URL || rc.url` and `token` to `process.env.VCUP_TOKEN || rc.token`.
3. **Failure** — If neither step yields a usable pair and the file is missing, print a sample config to stderr and exit with code `1`.

```text
                    ┌─────────────────────────┐
                    │ VCUP_API_URL set?       │
                    │ AND VCUP_TOKEN set?     │
                    └───────────┬─────────────┘
                          yes   │   no
                    ┌───────────▼───────────┐   ┌──────────────────────┐
                    │ return env only       │   │ ~/.vcuprc exists?    │
                    │ (skip rc file)        │   └──────────┬───────────┘
                    └───────────────────────┘         yes │ no
                                              ┌───────────▼──────────┐
                                              │ url  = env || rc.url │
                                              │ token= env || rc.token│
                                              └───────────┬──────────┘
                                                    missing file
                                              ┌───────────▼──────────┐
                                              │ stderr + exit(1)     │
                                              └──────────────────────┘
```

| `VCUP_API_URL` | `VCUP_TOKEN` | `~/.vcuprc` | Result |
| --- | --- | --- | --- |
| set | set | any | Env only; rc ignored |
| set | unset | present | `url` from env, `token` from rc |
| unset | set | present | `url` from rc, `token` from env |
| unset | unset | present | Both from rc |
| partial or none | partial or none | absent | Exit `1` with sample JSON |

<Note>
Setting only one env var without a rc file fails at step 3. Use a rc file for split storage (e.g. token in env, URL on disk) or export both env vars.
</Note>

## Match client settings to a deployment

After [Deploy on Vercel](/deploy-vercel), align three values:

| Client field | Server source | Must match |
| --- | --- | --- |
| `url` / `VCUP_API_URL` | Deployment hostname | Origin that serves `/api/upload` and `/api/delete` on your project |
| `token` / `VCUP_TOKEN` | Project env `VCUP_TOKEN` | Exact string; upload and delete handlers compare `Authorization: Bearer` to `process.env.VCUP_TOKEN` |

<Steps>
<Step title="Copy deployment URL">
Use the Vercel project URL (e.g. `https://your-project.vercel.app`) as `url`. For a custom domain, use that host instead—see [Custom domain](/custom-domain).
</Step>
<Step title="Copy server token">
Set the same secret in Vercel **Environment Variables** as `VCUP_TOKEN` and in `~/.vcuprc` as `token` (or export `VCUP_TOKEN` locally).
</Step>
<Step title="Write config">
Create `~/.vcuprc` or export env vars. Prefer env in CI; prefer rc on a developer machine.
</Step>
<Step title="Verify">
Run `vcup --help` (no config required), then upload a small file. A `401` means token mismatch; connection errors often mean wrong `url`.
</Step>
</Steps>

### URL shape

The CLI concatenates paths as `config.url` + `/api/upload` (and `/api/delete`). Use a base origin without a trailing slash, e.g. `https://my-vcup.vercel.app`, not `https://my-vcup.vercel.app/`.

### Proxy URLs vs API URL

`url` targets the **API**, not the blob CDN. Upload responses include a proxy link `${baseUrl}/f/${slug}` where `baseUrl` on the server is `VCUP_BASE_URL` or inferred from request headers—not from your rc `url`. If printed links should use a custom public host, set server `VCUP_BASE_URL`; your rc `url` can still point at the default deployment for API calls. Details: [Proxy and raw URLs](/proxy-and-raw-urls), [Custom domain](/custom-domain).

### Optional `BLOB_STORE_URL` on the client

Not part of `loadConfig()`. Required only when running `vcup rm` with a proxy URL containing `/f/`; the CLI maps the slug to `${BLOB_STORE_URL}/${slug}` before calling delete. Use the raw blob URL from upload `--raw` to avoid setting it. See [Upload and delete](/upload-and-delete).

## Environment vs file

<Tabs>
<Tab title="~/.vcuprc">

```json title="Persistent local config"
{
  "url": "https://your-vcup-deployment.vercel.app",
  "token": "your-vcup-token"
}
```

</Tab>
<Tab title="Environment">

```bash title="Shell or CI"
export VCUP_API_URL="https://your-vcup-deployment.vercel.app"
export VCUP_TOKEN="your-vcup-token"
```

</Tab>
</Tabs>

Help text documents the same contract: `vcup --help` lists the rc shape and names `VCUP_API_URL` / `VCUP_TOKEN`.

## What config does not control

| Concern | Where it lives |
| --- | --- |
| Blob read/write credentials | Server `BLOB_READ_WRITE_TOKEN` |
| Proxy fetch target store | Server `BLOB_STORE_URL` |
| Public link host in upload JSON | Server `VCUP_BASE_URL` (optional) |
| Inline file serving at `/f/:slug` | Public; no client token |

## Errors and checks

| Symptom | Likely cause |
| --- | --- |
| `Missing config. Set VCUP_API_URL…` | No rc file and incomplete env pair |
| `Upload failed: 401` | Client `token` ≠ server `VCUP_TOKEN` |
| `Upload failed: 400` | Missing `X-Filename` (CLI always sets it for uploads) |
| Wrong host in printed share link | Server `VCUP_BASE_URL` / forward headers, not rc `url` |
| `Cannot resolve proxy URL without BLOB_STORE_URL` | `vcup rm` on proxy URL without client `BLOB_STORE_URL` |

<Tip>
For local API testing with `vercel dev`, point `url` at the dev server origin and use the same `VCUP_TOKEN` as in your local `.env`. See [Local development](/local-development).
</Tip>

## Related pages

<CardGroup>
<Card title="Quickstart" href="/quickstart">
Configure, upload one file, and open the proxy URL.
</Card>
<Card title="Authentication" href="/authentication">
How `VCUP_TOKEN` is checked on upload and delete.
</Card>
<Card title="Deploy on Vercel" href="/deploy-vercel">
Server env vars to copy into client config.
</Card>
<Card title="Configuration reference" href="/configuration-reference">
Full client and server key listing.
</Card>
<Card title="Environment variables" href="/environment-variables">
Client vs server variable matrix.
</Card>
<Card title="Troubleshooting" href="/troubleshooting">
Config-related failure modes and HTTP statuses.
</Card>
</CardGroup>

---

## 09. Custom domain

> Point a subdomain at Vercel, set ~/.vcuprc url to the custom host, and how VCUP_BASE_URL affects upload response proxy URLs.

- Page Markdown: https://grok-wiki.com/public/docs/maxleiter-vcup-05c16ae77ebb/pages/09-custom-domain.md
- Generated: 2026-06-03T07:39:52.066Z

### Source Files

- `README.md`
- `api/upload.ts`
- `cli.ts`
- `vercel.json`

---
title: "Custom domain"
description: "Point a subdomain at Vercel, set ~/.vcuprc url to the custom host, and how VCUP_BASE_URL affects upload response proxy URLs."
---

vcup serves inline proxy links at `{base}/f/{slug}` on whatever host fronts the Vercel deployment; optional custom domains attach to the same project, while client `url` / `VCUP_API_URL` targets the API and server `VCUP_BASE_URL` (or request `Host` headers) shapes the proxy URL returned from `POST /api/upload`.

## Two configuration surfaces

Custom domains touch **client** and **server** settings independently. Both must agree on the hostname you want in shareable links when you are not relying on automatic header inference.

| Surface | Setting | Scope | Effect |
| --- | --- | --- | --- |
| Client | `~/.vcuprc` → `url` or `VCUP_API_URL` | CLI upload/delete | Base URL for `POST {url}/api/upload` and `DELETE {url}/api/delete` |
| Server | `VCUP_BASE_URL` (optional) | Vercel project env | Overrides the origin used in the JSON `url` field after upload |
| Server | (default) | Upload handler | `${x-forwarded-proto}://${host}` from the incoming upload request |

The `raw` field in the upload response is always the Vercel Blob store URL from `put()`; it is not rewritten by `VCUP_BASE_URL` or custom domains.

<Info>
Proxy file serving (`GET /f/:slug` → `api/f`) is defined in `vercel.json` and runs on every domain attached to the deployment, not only the default `*.vercel.app` hostname.
</Info>

## Attach a subdomain on Vercel

<Steps>
<Step title="Add the domain to the project">

From the project directory (or any directory linked to the deployment), register the hostname:

```bash
vercel domains add files.yourdomain.com
```

Replace `files.yourdomain.com` with your chosen subdomain.

</Step>
<Step title="Create the DNS record">

Add a CNAME for the subdomain label (e.g. `files`) pointing at `cname.vercel-dns.com`, as documented in the repository README.

</Step>
<Step title="Wait for Vercel to provision TLS">

Confirm the domain shows as active in the Vercel project **Settings → Domains** before testing uploads or opening `/f/` links on the new host.

</Step>
</Steps>

<Warning>
These steps configure DNS and Vercel routing only. Blob storage (`BLOB_STORE_URL`, `BLOB_READ_WRITE_TOKEN`) and auth (`VCUP_TOKEN`) are unchanged; see [Deploy on Vercel](/deploy-vercel) for the base deployment checklist.
</Warning>

## Point the CLI at the custom host

After the domain is live, set the client base URL to the custom origin (no trailing path). The CLI posts uploads and deletes to `{url}/api/...`.

<CodeGroup>
```json title="~/.vcuprc"
{
  "url": "https://files.yourdomain.com",
  "token": "your-vcup-token"
}
```

```bash title="Environment variables"
export VCUP_API_URL="https://files.yourdomain.com"
export VCUP_TOKEN="your-vcup-token"
```
</CodeGroup>

<ParamField body="url" type="string" required>
Deployment origin used as the API base. Must match the host where `/api/upload` and `/api/delete` are reachable (custom domain or `*.vercel.app`).
</ParamField>

Environment variables take precedence when **both** `VCUP_API_URL` and `VCUP_TOKEN` are set; otherwise `~/.vcuprc` supplies missing fields. See [Configure the CLI](/configure-cli) for the full precedence rules.

<Check>
Verify: `vcup /path/to/small.txt` prints a proxy URL whose host is `files.yourdomain.com` when client and server settings are aligned (next section).
</Check>

## How VCUP_BASE_URL shapes upload proxy URLs

`POST /api/upload` returns JSON with `url` (proxy) and `raw` (blob store). The proxy path is built server-side:

```text
url  = {baseUrl}/f/{slug}
raw  = {blob.url from @vercel/blob put}
```

`baseUrl` is chosen in this order:

1. `process.env.VCUP_BASE_URL` when set on the Vercel project
2. Otherwise `` `${req.headers["x-forwarded-proto"] || "http"}://${req.headers.host}` `` from the upload request

```mermaid
sequenceDiagram
  participant CLI as cli.ts
  participant API as api/upload.ts
  participant Blob as Vercel Blob

  CLI->>API: POST /api/upload (Host: files.example.com)
  API->>Blob: put(filename, stream)
  Blob-->>API: blob.url
  Note over API: slug = pathname of blob.url
  alt VCUP_BASE_URL set
    API-->>CLI: { url: VCUP_BASE_URL/f/slug, raw }
  else infer from request
    API-->>CLI: { url: proto://host/f/slug, raw }
  end
```

### When to set VCUP_BASE_URL

| Scenario | Set `VCUP_BASE_URL`? | Typical result |
| --- | --- | --- |
| CLI `url` is the custom domain; uploads hit that host | Optional | Inferred `baseUrl` matches custom domain via `Host` / `x-forwarded-proto` |
| CLI still uses `*.vercel.app` but shared links should use the custom domain | **Yes** — `https://files.yourdomain.com` | Proxy `url` uses custom host even though API requests go to `vercel.app` |
| Multiple domains on one deployment; force one canonical link host | **Yes** | Avoids whichever `Host` header arrived on upload |
| Local `vercel dev` without trustworthy forwarded headers | Often helpful | Explicit base avoids `http://localhost` in returned URLs |

Set `VCUP_BASE_URL` in the Vercel project **Environment Variables** (Production / Preview as needed). It is not listed in `.env.example` but is read directly in `api/upload.ts`. Use a full origin with scheme, no trailing slash, e.g. `https://files.yourdomain.com`.

<ResponseField name="url" type="string">
Proxy link: `{baseUrl}/f/{slug}` where `slug` is the blob pathname (leading `/` stripped) from the stored object URL.
</ResponseField>

<ResponseField name="raw" type="string">
Direct `https://…public.blob.vercel-storage.com/…` URL from Vercel Blob; unchanged by custom domain or `VCUP_BASE_URL`.
</ResponseField>

<Tip>
Use `vcup --raw` when you need the blob URL in scripts; use the default proxy `url` for browser-inline sharing. See [Proxy and raw URLs](/proxy-and-raw-urls).
</Tip>

## End-to-end hostname alignment

```text
  DNS CNAME  files ──► cname.vercel-dns.com
                              │
                              ▼
                    Vercel project (same as *.vercel.app)
                    ├── /api/upload, /api/delete  ◄── CLI config.url
                    └── /f/:slug ──► api/f        ◄── proxy url host
```

- **Opening** a printed `url` only requires the proxy host to route to this deployment; `api/f` fetches content via `BLOB_STORE_URL` + slug, not via the link hostname’s path alone.
- **Uploading** requires `config.url` to reach the authenticated API on that deployment.
- **Printing** the desired host in `url` requires either uploading through that host (header inference) or setting `VCUP_BASE_URL`.

## Mismatches and verification

| Symptom | Likely cause | Fix |
| --- | --- | --- |
| Printed link uses `*.vercel.app` but you use a custom domain | Uploads sent to default deployment URL; `VCUP_BASE_URL` unset | Set client `url` to custom host and/or set `VCUP_BASE_URL` |
| Upload 401 | `VCUP_TOKEN` mismatch | Match CLI token to project env (see [Authentication](/authentication)) |
| Proxy link 500 on GET | `BLOB_STORE_URL` missing on server | Set in Vercel env per [Deploy on Vercel](/deploy-vercel) |
| `vcup rm` on proxy URL fails locally | `BLOB_STORE_URL` unset in **client** env | Pass raw blob URL or set client `BLOB_STORE_URL` ([Upload and delete](/upload-and-delete)) |

<RequestExample>
```bash
# Upload through custom domain (client config)
vcup screenshot.png
```

```text
https://files.yourdomain.com/f/myfile-abc123.png
```
</RequestExample>

Open the printed URL in a browser; a `200` response with `Content-Disposition: inline` confirms the rewrite and `api/f` proxy path on that host.

## What custom domains do not change

- **Blob storage location** — Objects remain in the configured Vercel Blob store; `raw` URLs stay on `*.public.blob.vercel-storage.com`.
- **Auth** — `VCUP_TOKEN` Bearer checks on upload/delete are unchanged.
- **Public proxy route** — `GET /f/:slug` stays unauthenticated; only upload/delete require the token.
- **Delete API** — Still `DELETE {config.url}/api/delete` with `X-Blob-Url`; custom domain does not remove the client `BLOB_STORE_URL` requirement for resolving proxy URLs to blob URLs.

## Related pages

<CardGroup>
<Card title="Configure the CLI" href="/configure-cli">
`~/.vcuprc` schema, `VCUP_API_URL` / `VCUP_TOKEN` precedence, and matching client settings to your deployment.
</Card>
<Card title="Environment variables" href="/environment-variables">
Server `VCUP_BASE_URL`, `BLOB_STORE_URL`, and client overrides in one reference.
</Card>
<Card title="Proxy and raw URLs" href="/proxy-and-raw-urls">
`url` vs `raw`, `/f` rewrites, and inline serving behavior.
</Card>
<Card title="Deploy on Vercel" href="/deploy-vercel">
Initial deploy, Blob store wiring, and required server env vars before adding a domain.
</Card>
</CardGroup>

---

## 10. Upload and delete files

> File and stdin uploads, progress streaming, --raw flag, vcup rm with proxy or raw URLs, and BLOB_STORE_URL requirement for proxy deletes.

- Page Markdown: https://grok-wiki.com/public/docs/maxleiter-vcup-05c16ae77ebb/pages/10-upload-and-delete-files.md
- Generated: 2026-06-03T07:39:39.575Z

### Source Files

- `cli.ts`
- `README.md`
- `api/upload.ts`
- `api/delete.ts`
- `api/f.ts`

---
title: "Upload and delete files"
description: "File and stdin uploads, progress streaming, --raw flag, vcup rm with proxy or raw URLs, and BLOB_STORE_URL requirement for proxy deletes."
---

The `vcup` CLI in `cli.ts` uploads file or stdin bytes to `POST /api/upload` with a chunked stderr progress bar, prints either the proxy URL (`url`) or Vercel Blob URL (`raw`), and deletes objects via `vcup rm` by sending the resolved blob URL to `DELETE /api/delete`. Proxy URLs (`/f/...`) require client-side `BLOB_STORE_URL` to map slug → store URL before delete; raw URLs skip that step.

## Prerequisites

| Requirement | Where it applies |
| --- | --- |
| `~/.vcuprc` or `VCUP_API_URL` + `VCUP_TOKEN` | Every upload and delete |
| Deployed vcup API with `VCUP_TOKEN` matching the CLI token | `api/upload.ts`, `api/delete.ts` |
| `BLOB_STORE_URL` on the **client** | `vcup rm` when the argument is a proxy URL containing `/f/` |
| `BLOB_STORE_URL` on the **server** | Serving files at `/f/:slug` via `api/f.ts` (not upload itself) |

<Note>
Upload and delete handlers authenticate with the same shared secret. Configure the CLI before running commands — see [Configure the CLI](/configure-cli).
</Note>

## Upload a file

<Steps>
<Step title="Pick a path">
Pass one file path as the first non-flag argument. The CLI resolves it, rejects missing paths and directories, reads the full file into memory, and sets `X-Filename` to `basename(filePath)`.
</Step>
<Step title="Stream with progress">
`progressStream` emits 64 KiB chunks to the request body while rewriting stderr with a percentage bar, transferred/total sizes, and throughput. When the stream finishes, the progress line is cleared.
</Step>
<Step title="Read the printed URL">
On success the CLI prints one line to stdout: the proxy URL by default, or the raw blob URL when `--raw` is present.
</Step>
</Steps>

<RequestExample>
```bash
vcup screenshot.png
```
</RequestExample>

<ResponseExample>
```text
https://your-vcup.vercel.app/f/screenshot-abc123.png
```
</ResponseExample>

The server stores the object with `@vercel/blob` `put()`, `access: "public"`, and `addRandomSuffix: true`, then returns JSON:

<ResponseField name="url" type="string">
Proxy URL: `{baseUrl}/f/{slug}` where `slug` is the blob pathname (random suffix included).
</ResponseField>

<ResponseField name="raw" type="string">
Direct Vercel Blob URL returned by `put()`.
</ResponseField>

`baseUrl` is `VCUP_BASE_URL` when set, otherwise `x-forwarded-proto` + `Host` from the upload request.

## Upload from stdin

When no file argument is given and stdin is not a TTY, the CLI buffers all stdin chunks, uses filename `paste.txt`, and runs the same upload path as a file upload.

<CodeGroup>
```bash title="Pipe text"
echo "hello world" | vcup
```
```bash title="Pipe a file"
cat logs.txt | vcup
```
</CodeGroup>

<Warning>
Stdin uploads always use `X-Filename: paste.txt`. There is no flag to override the name from the CLI.
</Warning>

## `--raw` output

<ParamField body="--raw" type="flag">
When present anywhere in the upload argument list, stdout receives `data.raw` instead of `data.url` from the upload JSON response.
</ParamField>

<RequestExample>
```bash
vcup --raw image.png
```
</RequestExample>

<ResponseExample>
```text
https://abc123.public.blob.vercel-storage.com/image-xyz.png
```
</ResponseExample>

Use `--raw` when you need the store URL for tooling, direct blob access, or `vcup rm` without setting client `BLOB_STORE_URL`.

## Upload request shape

The CLI sends:

| Header | Value |
| --- | --- |
| `Authorization` | `Bearer {token}` from config |
| `X-Filename` | Basename of file path, or `paste.txt` for stdin |

| Body | Behavior |
| --- | --- |
| ReadableStream | Chunked stream from in-memory file/stdin buffer; Bun sets `duplex: "half"` on `fetch` |

:::endpoint POST /api/upload
Authenticated streaming upload to Vercel Blob.

**Required headers:** `Authorization: Bearer <VCUP_TOKEN>`, `X-Filename`

**Success (200):** `Content-Type: application/json` body `{ "url": string, "raw": string }`

**Errors:** `401` invalid/missing token, `400` missing `x-filename`, `405` non-POST
:::

## Delete a file (`vcup rm`)

`vcup rm <url>` and `vcup delete <url>` are equivalent. The URL may be either form returned from upload:

| URL type | Example shape | Client resolution |
| --- | --- | --- |
| Proxy | `https://host/f/{slug}` | If path contains `/f/`, slug = segment after `/f/`; blob URL = `{BLOB_STORE_URL}/{slug}` |
| Raw | `https://….public.blob.vercel-storage.com/...` | Used as-is for `X-Blob-Url` |

<Steps>
<Step title="Resolve blob URL">
Proxy URLs need `process.env.BLOB_STORE_URL` on the machine running the CLI. Without it, the CLI exits with an error instructing you to pass the raw URL or set the variable.
</Step>
<Step title="Call delete API">
`DELETE {VCUP_API_URL}/api/delete` with `Authorization: Bearer {token}` and `X-Blob-Url: {blobUrl}`.
</Step>
<Step title="Confirm">
Success prints `Deleted` to stdout. Non-OK responses print status and body to stderr and exit `1`.
</Step>
</Steps>

<RequestExample>
```bash
# Proxy URL from default upload output
vcup rm https://your-vcup.vercel.app/f/paste-a1b2c3.txt

# Raw blob URL (no client BLOB_STORE_URL needed)
vcup rm https://abc123.public.blob.vercel-storage.com/paste-a1b2c3.txt
```
</RequestExample>

:::endpoint DELETE /api/delete
Authenticated blob deletion via `@vercel/blob` `del()`.

**Required headers:** `Authorization: Bearer <VCUP_TOKEN>`, `X-Blob-Url` (full blob store URL)

**Success (200):** body `Deleted`

**Errors:** `401` unauthorized, `400` missing `x-blob-url`, `405` non-DELETE
:::

### Why proxy deletes need `BLOB_STORE_URL`

Proxy links expose only the slug under `/f/`. The delete API expects the **blob store URL**, not the proxy host path. The CLI reconstructs:

```text
blobUrl = ${BLOB_STORE_URL}/${slug}
```

where `slug` is everything after `/f/` in the pasted URL (may include path segments from `addRandomSuffix`).

<Info>
Server `BLOB_STORE_URL` (in Vercel project env) powers `GET /f/:slug` in `api/f.ts`. Client `BLOB_STORE_URL` is a separate concern used only when deleting via proxy URL from your laptop or CI.
</Info>

## End-to-end flows

```mermaid
sequenceDiagram
  participant User
  participant CLI as cli.ts
  participant Upload as api/upload.ts
  participant Blob as Vercel Blob
  participant Delete as api/delete.ts

  User->>CLI: vcup file.png
  CLI->>CLI: progressStream (64KB chunks)
  CLI->>Upload: POST /api/upload + X-Filename
  Upload->>Blob: put(filename, stream)
  Blob-->>Upload: blob.url
  Upload-->>CLI: JSON url + raw
  CLI-->>User: stdout url (or raw)

  User->>CLI: vcup rm proxy URL
  alt URL contains /f/
    CLI->>CLI: slug + BLOB_STORE_URL
  end
  CLI->>Delete: DELETE + X-Blob-Url
  Delete->>Blob: del(url)
  Delete-->>CLI: 200 Deleted
  CLI-->>User: Deleted
```

## CLI surface summary

| Command | stdin | Output |
| --- | --- | --- |
| `vcup <file>` | No (file path required) | Proxy `url` |
| `vcup --raw <file>` | No | `raw` blob URL |
| `pipe \| vcup` | Yes (`paste.txt`) | Proxy `url` |
| `vcup rm <url>` | No | `Deleted` |
| `vcup --help` | — | Help text |

| Failure (CLI) | Cause |
| --- | --- |
| Exit `1` missing config | No `~/.vcuprc` and incomplete env vars |
| `File not found` | Bad path |
| `Cannot upload a directory` | Directory path |
| `Cannot resolve proxy URL without BLOB_STORE_URL` | `vcup rm` on `/f/` URL without client env |
| `Upload failed` / `Delete failed` | Non-OK HTTP from API |

## Verification

<Steps>
<Step title="Upload">
Run `vcup <small-file>` and open the printed `/f/...` URL in a browser. The proxy should return `200` with `Content-Disposition: inline` when server `BLOB_STORE_URL` is set.
</Step>
<Step title="Raw URL">
Run `vcup --raw <file>` and confirm the hostname matches your blob store public URL.
</Step>
<Step title="Delete">
Run `vcup rm` with the same URL you printed (proxy or raw). A second delete or fetch should fail at the store/proxy layer.
</Step>
</Steps>

<Tip>
For proxy deletes from automation, either export client `BLOB_STORE_URL` to match the deployment store URL, or persist and pass `raw` URLs from `vcup --raw`.
</Tip>

## Related pages

<CardGroup>
<Card title="Quickstart" href="/quickstart">
First upload, open the proxy link, and optional `--raw` output.
</Card>
<Card title="Proxy and raw URLs" href="/proxy-and-raw-urls">
`url` vs `raw`, `/f` rewrite, and when each URL type is required.
</Card>
<Card title="CLI reference" href="/cli-reference">
Full command list, headers, exit codes, and help text.
</Card>
<Card title="API reference" href="/api-reference">
Upload, delete, and proxy GET handlers with status codes.
</Card>
<Card title="Environment variables" href="/environment-variables">
Server and client `BLOB_STORE_URL`, `VCUP_TOKEN`, and `VCUP_BASE_URL`.
</Card>
<Card title="Troubleshooting" href="/troubleshooting">
401/400/500/502 cases, directory uploads, and proxy delete errors.
</Card>
</CardGroup>

---

## 11. CLI reference

> vcup subcommands and flags, stdin behavior, default paste.txt filename, exit codes, help text, and Authorization/X-Filename headers sent to the API.

- Page Markdown: https://grok-wiki.com/public/docs/maxleiter-vcup-05c16ae77ebb/pages/11-cli-reference.md
- Generated: 2026-06-03T07:40:16.244Z

### Source Files

- `cli.ts`
- `package.json`
- `README.md`

---
title: "CLI reference"
description: "vcup subcommands and flags, stdin behavior, default paste.txt filename, exit codes, help text, and Authorization/X-Filename headers sent to the API."
---

The `@maxleiter/vcup` package publishes a single Bun entrypoint (`cli.ts`) as the `vcup` binary. The CLI loads API credentials, uploads files or stdin to `POST /api/upload` with Bearer auth and an `X-Filename` header, prints either the proxy or raw URL from the JSON response, and deletes blobs via `DELETE /api/delete` with `X-Blob-Url`.

## Binary and runtime

| Property | Value |
| --- | --- |
| Package | `@maxleiter/vcup` |
| Binary name | `vcup` |
| Entry file | `cli.ts` (shebang `#!/usr/bin/env bun`) |
| Runtime | Bun (streaming upload uses Bun `duplex: "half"`) |

Install globally with `bun install -g @maxleiter/vcup` or `npm install -g @maxleiter/vcup`. The published `bin` field maps `vcup` directly to `./cli.ts`.

## Command synopsis

```text
vcup [options] [<file>]
vcup rm <url>
vcup delete <url>    # alias for rm
```

| Invocation | Action |
| --- | --- |
| `vcup <path>` | Upload a single file; basename becomes `X-Filename` |
| `vcup` (stdin piped) | Upload stdin body; filename fixed to `paste.txt` |
| `vcup --raw <path>` | Upload; print `raw` blob URL instead of proxy `url` |
| `vcup rm <url>` | Delete by proxy URL (`/f/...`) or raw blob URL |
| `vcup --help`, `vcup -h` | Print built-in help (exit 0) |
| `vcup` (no args, TTY stdin) | Print help (exit 0) |

There is no subcommand name for upload: any first argument that is not `rm`, `delete`, `--help`, or `-h` is treated as an upload path (after `--raw` is stripped).

## Flags

<ParamField body="--raw" type="boolean">
When present on an upload invocation, stdout receives the `raw` field from the upload JSON response (Vercel Blob public URL) instead of the proxy `url` (`{base}/f/{slug}`).
</ParamField>

<ParamField body="--help" type="boolean">
Alias: `-h`. Prints usage and configuration hints, then exits without calling the API.
</ParamField>

<Note>
The CLI does not define short flags for upload paths, verbosity, or custom filenames. Filename always comes from the file basename or `paste.txt` for stdin.
</Note>

## Stdin uploads

Upload-from-stdin activates when **no file path argument** remains after removing `--raw` **and** `process.stdin.isTTY` is false (pipe or redirect).

<Steps>
<Step title="Detect non-TTY stdin">
If `fileArgs` is empty and stdin is not a TTY, the CLI reads the full stdin stream into memory in chunks.
</Step>
<Step title="Assign default filename">
The upload uses the literal filename `paste.txt` for the `X-Filename` header, regardless of pipe content.
</Step>
<Step title="Stream to API">
The buffered body is sent through the same progress stream and `POST /api/upload` path as file uploads.
</Step>
</Steps>

Examples:

```bash
echo "hello world" | vcup
cat logs.txt | vcup
```

<Warning>
If you run `vcup --raw` with no file and a TTY stdin (no pipe), the CLI prints help and exits with code **1**. Piping is required for stdin-only uploads.
</Warning>

## File uploads

When a path argument is present:

- The path is resolved with `resolve()` relative to the current working directory.
- Missing files → stderr message, exit **1**.
- Directories → stderr `Cannot upload a directory`, exit **1**.
- File contents are read fully into memory, then chunked for upload progress on stderr.
- `X-Filename` is `basename(filePath)`.

Progress displays on **stderr** as a 33-character bar, percent, sizes, and speed; stdout stays clean for the final URL only.

## Delete (`rm`)

```bash
vcup rm https://your-host/f/abc123/filename.png
vcup rm https://....public.blob.vercel-storage.com/...
```

| Input URL type | CLI behavior |
| --- | --- |
| Contains `/f/` | Treat as proxy URL; extract slug after `/f/`; requires client `BLOB_STORE_URL` to build `{BLOB_STORE_URL}/{slug}` |
| Otherwise | Passed through as the blob URL in `X-Blob-Url` |

The delete request is `DELETE {config.url}/api/delete` with headers below. On success the CLI prints `Deleted` to stdout.

<Warning>
Deleting via a proxy URL without `BLOB_STORE_URL` in the **client** environment fails before any API call, with instructions to pass the raw blob URL or set `BLOB_STORE_URL`.
</Warning>

## Configuration loading

Before upload or delete, `loadConfig()` resolves API base URL and token:

1. If **both** `VCUP_API_URL` and `VCUP_TOKEN` are set → use them.
2. Else if `~/.vcuprc` exists → parse JSON; env vars override individual keys when only one is set.
3. Else → stderr with example JSON, exit **1**.

```json
{
  "url": "https://your-vcup.vercel.app",
  "token": "your-token"
}
```

The `url` value is the deployment origin (no trailing path); requests append `/api/upload` or `/api/delete`.

## API requests from the CLI

### Upload

:::endpoint POST /api/upload
CLI-initiated upload to the configured deployment.
:::

<RequestExample>

```http
POST https://your-vcup.vercel.app/api/upload HTTP/1.1
Authorization: Bearer <VCUP_TOKEN>
X-Filename: screenshot.png
Content-Type: application/octet-stream

<streaming body>
```

</RequestExample>

<ResponseExample>

```json
{
  "url": "https://your-vcup.vercel.app/f/abc123/screenshot.png",
  "raw": "https://....public.blob.vercel-storage.com/abc123/screenshot.png"
}
```

</ResponseExample>

| Header | Set by CLI | Required by server |
| --- | --- | --- |
| `Authorization` | `Bearer ${token}` from config | Yes; must match server `VCUP_TOKEN` |
| `X-Filename` | File basename or `paste.txt` | Yes; missing → 400 |

The request body is a `ReadableStream` of 64 KiB chunks with `duplex: "half"` (Bun). The server reads the raw stream into Vercel Blob `put()` with `addRandomSuffix: true`.

On failure the CLI prints `Upload failed: {status} {body}` to stderr and exits **1**. On success it prints one line to stdout: `data.url` by default, or `data.raw` when `--raw` was passed.

### Delete

:::endpoint DELETE /api/delete
CLI-initiated blob deletion.
:::

<RequestExample>

```http
DELETE https://your-vcup.vercel.app/api/delete HTTP/1.1
Authorization: Bearer <VCUP_TOKEN>
X-Blob-Url: https://....public.blob.vercel-storage.com/<slug>
```

</RequestExample>

| Header | Set by CLI | Required by server |
| --- | --- | --- |
| `Authorization` | `Bearer ${token}` | Yes |
| `X-Blob-Url` | Resolved blob store URL | Yes; missing → 400 |

Success: stdout `Deleted`. Failure: stderr `Delete failed: {status} {body}`, exit **1**.

## Built-in help text

Running `vcup --help`, `vcup -h`, or `vcup` with no arguments and a TTY stdin prints:

```text
vcup — simple file sharing via Vercel Blob

Usage:
  vcup <file>              Upload a file
  echo "hi" | vcup         Upload from stdin
  vcup rm <url>            Delete a file by its proxy or raw URL
  vcup --raw <file>        Print raw blob URL instead of proxy URL
  vcup --help              Show this help

Config (~/.vcuprc):
  { "url": "https://your-vcup.vercel.app", "token": "your-token" }

Or set VCUP_API_URL and VCUP_TOKEN environment variables.
```

Help does not load config or contact the API. Exit code **0**.

## Exit codes

| Code | When |
| --- | --- |
| **0** | Help displayed; successful upload (URL on stdout); successful delete (`Deleted`) |
| **1** | Missing config; file not found; directory upload; `vcup rm` without URL; proxy delete without `BLOB_STORE_URL`; non-OK upload/delete HTTP response; upload with `--raw` only and TTY stdin (no file, no pipe) |

The CLI does not define custom exit codes beyond **0** and **1**.

## Client environment variables

| Variable | Role in CLI |
| --- | --- |
| `VCUP_API_URL` | API origin (with `VCUP_TOKEN`, skips rc-only path when both set) |
| `VCUP_TOKEN` | Bearer token for upload and delete |
| `BLOB_STORE_URL` | Required to resolve proxy URLs (`/f/...`) into blob URLs for `vcup rm` |

Server-side variables (`BLOB_READ_WRITE_TOKEN`, `VCUP_BASE_URL`, etc.) affect API responses but are not read by `cli.ts` except indirectly via upload JSON.

## Sequence: upload with progress

```mermaid
sequenceDiagram
  participant User
  participant CLI as cli.ts
  participant API as POST /api/upload
  participant Blob as Vercel Blob

  User->>CLI: vcup file.png
  CLI->>CLI: loadConfig()
  CLI->>CLI: readFile + progressStream
  CLI->>API: Authorization + X-Filename + stream body
  API->>Blob: put(filename, stream)
  Blob-->>API: blob.url
  API-->>CLI: JSON url + raw
  CLI-->>User: stdout url (or raw if --raw)
```

## Related pages

<CardGroup>
<Card title="Configure the CLI" href="/configure-cli">
`~/.vcuprc` schema and env precedence used by `loadConfig()`.
</Card>
<Card title="Upload and delete" href="/upload-and-delete">
Workflow-oriented guide for uploads, `--raw`, and `vcup rm`.
</Card>
<Card title="API reference" href="/api-reference">
Server handlers for `/api/upload`, `/api/delete`, and `/f/:slug`.
</Card>
<Card title="Environment variables" href="/environment-variables">
Full client and server variable matrix.
</Card>
<Card title="Troubleshooting" href="/troubleshooting">
Common CLI failure modes and HTTP status codes.
</Card>
</CardGroup>

---

## 12. Configuration reference

> ~/.vcuprc keys, client env vars, server env vars, VCUP_BASE_URL override, and validation errors returned by handlers.

- Page Markdown: https://grok-wiki.com/public/docs/maxleiter-vcup-05c16ae77ebb/pages/12-configuration-reference.md
- Generated: 2026-06-03T07:40:27.267Z

### Source Files

- `cli.ts`
- `.env.example`
- `api/upload.ts`
- `api/f.ts`
- `api/delete.ts`
- `README.md`

---
title: "Configuration reference"
description: "~/.vcuprc keys, client env vars, server env vars, VCUP_BASE_URL override, and validation errors returned by handlers."
---

vcup splits configuration across a client-side `~/.vcuprc` (or `VCUP_API_URL` / `VCUP_TOKEN` env vars) that points the CLI at your deployment, and server-side Vercel environment variables that authenticate uploads/deletes, wire Vercel Blob, and shape proxy URLs in upload responses. API handlers in `api/upload.ts`, `api/delete.ts`, and `api/f.ts` return plain-text bodies with fixed status codes for validation failures; the CLI adds its own exit-time errors when config or delete preconditions are missing.

## Configuration surfaces

| Surface | Location | Consumed by |
| --- | --- | --- |
| `~/.vcuprc` | `$HOME/.vcuprc` | CLI (`loadConfig` in `cli.ts`) |
| Client env | Shell / CI | CLI |
| Server env | Vercel project / local `.env` | Serverless handlers + `@vercel/blob` |

```text
  Developer machine                    Vercel deployment
  -----------------                    ------------------
  ~/.vcuprc { url, token }  ------>   POST /api/upload  (VCUP_TOKEN)
  VCUP_API_URL, VCUP_TOKEN            DELETE /api/delete
  BLOB_STORE_URL (rm only)            GET /f/:slug -> api/f.ts

                                      BLOB_READ_WRITE_TOKEN (@vercel/blob)
                                      BLOB_STORE_URL (proxy fetch)
                                      VCUP_BASE_URL (upload JSON url field)
```

## `~/.vcuprc`

The CLI reads a JSON file at `~/.vcuprc` with two string fields. Both are required for a successful upload or delete unless environment variables supply them (see precedence below).

<ParamField body="url" type="string" required>
Deployment origin used as the API base. Requests go to `{url}/api/upload` and `{url}/api/delete`. Use your Vercel app URL or custom domain (no trailing path).
</ParamField>

<ParamField body="token" type="string" required>
Shared secret sent as `Authorization: Bearer {token}`. Must match the server `VCUP_TOKEN` value.
</ParamField>

Example (from CLI help and README):

```json
{
  "url": "https://your-vcup-deployment.vercel.app",
  "token": "your-vcup-token"
}
```

<Warning>
`loadConfig` does not validate JSON shape beyond `JSON.parse`. Missing `url` or `token` keys can produce `undefined` in fetch URLs if only partial env overrides are set.
</Warning>

## Client environment variables

| Variable | Required | Role |
| --- | --- | --- |
| `VCUP_API_URL` | For env-only config | Same as `~/.vcuprc` `url` |
| `VCUP_TOKEN` | For env-only config | Same as `~/.vcuprc` `token` |
| `BLOB_STORE_URL` | For `vcup rm` on proxy URLs only | Public blob store origin used to rewrite `/f/...` to `{store}/{slug}` before delete |

### Precedence (`loadConfig`)

1. If **both** `VCUP_API_URL` and `VCUP_TOKEN` are set, the CLI uses those values and does not read `~/.vcuprc`.
2. If `~/.vcuprc` exists, each field is `process.env.VCUP_* || rc.*` (env overrides per key).
3. Otherwise the CLI prints a sample `~/.vcuprc` JSON snippet and exits with code `1`.

<Note>
`VCUP_API_URL` controls where the CLI calls the API. It does **not** set the hostname embedded in upload response `url` — that comes from server `VCUP_BASE_URL` or the request host (see below).
</Note>

### CLI-only errors (not HTTP handlers)

| Condition | Message / behavior |
| --- | --- |
| No complete config | `Missing config. Set VCUP_API_URL and VCUP_TOKEN env vars, or create ~/.vcuprc:` + sample JSON; exit `1` |
| `vcup rm` with proxy URL, no `BLOB_STORE_URL` | `Cannot resolve proxy URL without BLOB_STORE_URL env var...`; exit `1` |
| Upload/delete HTTP failure | `Upload failed:` / `Delete failed:` + status + response body; exit `1` |

## Server environment variables

`.env.example` documents the deploy-time set; handlers reference a subset directly.

| Variable | Required | Used in code | Purpose |
| --- | --- | --- | --- |
| `BLOB_READ_WRITE_TOKEN` | Yes (Blob SDK) | Not referenced in handlers | Credentials for `@vercel/blob` `put` / `del` on Vercel |
| `BLOB_STORE_URL` | Yes for proxy | `api/f.ts`, CLI delete | Public store URL (e.g. `https://abc123.public.blob.vercel-storage.com`) to build blob fetch URLs |
| `VCUP_TOKEN` | Yes for protected routes | `api/upload.ts`, `api/delete.ts` | Bearer secret; if unset, all authenticated requests get `401` |
| `VCUP_BASE_URL` | No | `api/upload.ts` | Overrides origin in JSON `url` field (`{VCUP_BASE_URL}/f/{slug}`) |

`BLOB_READ_WRITE_TOKEN` is wired by the Vercel Blob integration and the deploy button env list in README; application code assumes it is present when `put` / `del` run.

## `VCUP_BASE_URL` override

On successful upload, the handler builds the proxy link:

```ts
const baseUrl =
  process.env.VCUP_BASE_URL ||
  `${req.headers["x-forwarded-proto"] || "http"}://${req.headers.host}`;
// response: { url: `${baseUrl}/f/${slug}`, raw: blob.url }
```

| Setting | Effect on `url` in upload JSON | Effect on `raw` |
| --- | --- | --- |
| Unset | `https?://{Host}/f/{slug}` from request headers | Vercel Blob URL from `put` |
| Set (e.g. `https://files.example.com`) | `{VCUP_BASE_URL}/f/{slug}` | Unchanged (always `blob.url`) |

<Tip>
For a custom public hostname, set `VCUP_BASE_URL` on the server **and** set client `url` / `VCUP_API_URL` to the same origin so CLI calls and printed links stay consistent.
</Tip>

## Handler validation and error responses

Handlers respond with **plain text** bodies (not JSON) for errors unless noted. Node lowercases incoming header names; the CLI sends `Authorization`, `X-Filename`, and `X-Blob-Url`.

### `POST /api/upload` (`api/upload.ts`)

| Status | Body | When |
| --- | --- | --- |
| `405` | `Method not allowed` | Method is not `POST` |
| `401` | `Unauthorized` | `VCUP_TOKEN` unset on server, or `Authorization: Bearer` value mismatch |
| `400` | `Missing x-filename header` | `x-filename` header absent |
| `200` | JSON `{ "url": string, "raw": string }` | Success |

Protected check (both must pass): server has `process.env.VCUP_TOKEN` **and** bearer token equals it.

### `DELETE /api/delete` (`api/delete.ts`)

| Status | Body | When |
| --- | --- | --- |
| `405` | `Method not allowed` | Method is not `DELETE` |
| `401` | `Unauthorized` | Same Bearer rules as upload |
| `400` | `Missing x-blob-url header` | `x-blob-url` header absent |
| `200` | `Deleted` | Success (plain text) |

The CLI sets `X-Blob-Url` to the raw blob URL (after optional proxy→store rewrite).

### `GET /f/:slug` → `api/f.ts` (public, no Bearer)

| Status | Body | When |
| --- | --- | --- |
| `405` | `Method not allowed` | Method is not `GET` |
| `400` | `Missing file slug` | Path has no slug after `/f/` |
| `500` | `BLOB_STORE_URL not configured` | `BLOB_STORE_URL` unset |
| `{upstream}` | `Not found` | Blob fetch returned non-OK (e.g. `404`) |
| `502` | `Failed to fetch file` | Fetch threw |
| `200` | Streamed body | Success; `Content-Disposition: inline`, long cache |

Slug extraction: pathname with `/^\/f\//` removed via `vercel.json` rewrite `{ "source": "/f/:slug*", "destination": "/api/f" }`.

## Matching client and server settings

<Steps>
<Step title="Set server secrets on Vercel">
Add `BLOB_READ_WRITE_TOKEN`, `BLOB_STORE_URL`, and `VCUP_TOKEN` from Storage / your chosen secret. Optionally set `VCUP_BASE_URL` for a custom public origin.
</Step>
<Step title="Point the CLI at the deployment">
Create `~/.vcuprc` with `url` = deployment origin and `token` = the same string as `VCUP_TOKEN`, or export `VCUP_API_URL` and `VCUP_TOKEN`.
</Step>
<Step title="Optional: proxy deletes from the CLI">
If you delete by proxy URL (`.../f/...`), set client `BLOB_STORE_URL` to the store public URL, or pass the raw blob URL from upload `--raw`.
</Step>
</Steps>

## Quick reference: who reads what

| Name | Client | Server upload | Server delete | Server proxy (`/f`) |
| --- | --- | --- | --- | --- |
| `url` / `VCUP_API_URL` | API base | — | — | — |
| `token` / `VCUP_TOKEN` | Bearer | Auth | Auth | — |
| `VCUP_BASE_URL` | — | Response `url` | — | — |
| `BLOB_STORE_URL` | `rm` rewrite | — | — | Fetch blob |
| `BLOB_READ_WRITE_TOKEN` | — | Blob `put` | Blob `del` | — |

## Related pages

<CardGroup>
<Card title="Configure the CLI" href="/configure-cli">
`~/.vcuprc` schema, env precedence, and aligning client settings with your deployment.
</Card>
<Card title="Environment variables" href="/environment-variables">
Focused index of server and client variable names.
</Card>
<Card title="Authentication" href="/authentication">
Bearer token flow on upload and delete; public proxy route.
</Card>
<Card title="Custom domain" href="/custom-domain">
`VCUP_BASE_URL` plus client `url` for a single public hostname.
</Card>
<Card title="API reference" href="/api-reference">
Full route contracts, headers, and response shapes.
</Card>
<Card title="Troubleshooting" href="/troubleshooting">
Common misconfigurations mapped to status codes and CLI errors.
</Card>
</CardGroup>

---

## 13. API reference

> POST /api/upload, DELETE /api/delete, and GET /f/:slug (via rewrite): methods, headers, status codes, JSON response shape, and streaming behavior.

- Page Markdown: https://grok-wiki.com/public/docs/maxleiter-vcup-05c16ae77ebb/pages/13-api-reference.md
- Generated: 2026-06-03T07:40:24.015Z

### Source Files

- `api/upload.ts`
- `api/delete.ts`
- `api/f.ts`
- `vercel.json`
- `cli.ts`

---
title: "API reference"
description: "POST /api/upload, DELETE /api/delete, and GET /f/:slug (via rewrite): methods, headers, status codes, JSON response shape, and streaming behavior."
---

vcup exposes three HTTP surfaces on a Vercel deployment: authenticated `POST /api/upload` and `DELETE /api/delete` handlers that stream through `@vercel/blob`, and a public `GET /f/:slug` path rewritten to `api/f` that proxies blob storage with inline `Content-Disposition`. All handlers disable the default body parser and return plain-text error bodies except upload success, which returns JSON.

## Endpoint inventory

| Method | Path | Auth | Handler | Purpose |
|--------|------|------|---------|---------|
| `POST` | `/api/upload` | Bearer `VCUP_TOKEN` | `api/upload.ts` | Stream body to Vercel Blob; return proxy and raw URLs |
| `DELETE` | `/api/delete` | Bearer `VCUP_TOKEN` | `api/delete.ts` | Delete blob by store URL |
| `GET` | `/f/:slug*` | None | `api/f.ts` (via rewrite) | Proxy blob bytes with inline rendering |

<Note>
`vercel.json` rewrites `/f/:slug*` to `/api/f`. Clients and the upload response use the public `/f/...` path; the rewrite is transparent to callers.
</Note>

## Request flow

```mermaid
sequenceDiagram
  participant Client
  participant Upload as api/upload
  participant Blob as Vercel Blob
  participant Proxy as api/f
  participant Delete as api/delete

  Client->>Upload: POST body stream + Bearer + X-Filename
  Upload->>Blob: put(filename, stream, addRandomSuffix)
  Blob-->>Upload: blob.url
  Upload-->>Client: JSON url + raw

  Client->>Proxy: GET /f/slug
  Proxy->>Blob: fetch(BLOB_STORE_URL/slug)
  Blob-->>Proxy: body stream
  Proxy-->>Client: streamed bytes + Content-Disposition inline

  Client->>Delete: DELETE + Bearer + X-Blob-Url
  Delete->>Blob: del(url)
  Delete-->>Client: 200 Deleted
```

## Shared behavior

### Body parsing

Upload and delete export `config = { api: { bodyParser: false } }`, so the Node request stream is passed through without buffering by the platform parser. Upload passes `req` directly to `put()`; delete has no body.

### Authentication

Upload and delete require `Authorization: Bearer <token>` where the token equals `process.env.VCUP_TOKEN`. If `VCUP_TOKEN` is unset or the bearer value does not match, the handler responds with **401** and body `Unauthorized`. The proxy route does not check a token.

### Error response format

Except for upload success (**200** `application/json`), failures use `res.end()` with a short plain-text message and no JSON envelope. Status codes are set on `res.statusCode` before the body is written.

---

:::endpoint POST /api/upload
Stream the request body into Vercel Blob storage and return proxy and raw URLs.
:::

### Method

Only `POST` is accepted. Any other method returns **405** with body `Method not allowed`.

### Request headers

<ParamField header="Authorization" type="string" required>
Bearer token. The value after `Bearer ` must equal `VCUP_TOKEN`.
</ParamField>

<ParamField header="X-Filename" type="string" required>
Logical filename passed to `put()`. Blob storage may append a random suffix to the stored path.
</ParamField>

### Request body

The raw request stream is the upload payload. There is no `Content-Type` requirement in the handler; the CLI sends a `ReadableStream` with Bun `duplex: "half"` for streaming uploads.

### Success response

**200** `Content-Type: application/json`

<ResponseField name="url" type="string">
Proxy URL: `{baseUrl}/f/{slug}` where `slug` is the pathname of the blob URL (leading `/` stripped). `baseUrl` is `VCUP_BASE_URL` if set, otherwise `{x-forwarded-proto || "http"}://{host}` from the incoming request.
</ResponseField>

<ResponseField name="raw" type="string">
Direct Vercel Blob public URL returned by `put()`.
</ResponseField>

<RequestExample>

```bash
curl -X POST "https://your-deployment.vercel.app/api/upload" \
  -H "Authorization: Bearer your-vcup-token" \
  -H "X-Filename: screenshot.png" \
  --data-binary @screenshot.png
```

</RequestExample>

<ResponseExample>

```json
{
  "url": "https://your-deployment.vercel.app/f/screenshot-abc123.png",
  "raw": "https://abc123.public.blob.vercel-storage.com/screenshot-abc123.png"
}
```

</ResponseExample>

### Blob write options

`put(filename, req, { access: "public", addRandomSuffix: true })` stores the object publicly and lets the SDK randomize the final path segment.

### Error responses

| Status | Body |
|--------|------|
| 401 | `Unauthorized` |
| 400 | `Missing x-filename header` |
| 405 | `Method not allowed` |

<Warning>
The handler does not catch `put()` failures in code. SDK or network errors surface as platform-level **500** responses rather than a documented JSON error shape.
</Warning>

---

:::endpoint DELETE /api/delete
Delete a blob object by its store URL.
:::

### Method

Only `DELETE` is accepted. Other methods return **405** `Method not allowed`.

### Request headers

<ParamField header="Authorization" type="string" required>
Same Bearer check as upload against `VCUP_TOKEN`.
</ParamField>

<ParamField header="X-Blob-Url" type="string" required>
Full blob URL to pass to `del(url)` from `@vercel/blob`. Must be the raw store URL, not the `/f/...` proxy path.
</ParamField>

### Request body

None.

### Success response

**200** with plain-text body `Deleted`. No `Content-Type` header is set explicitly.

<RequestExample>

```bash
curl -X DELETE "https://your-deployment.vercel.app/api/delete" \
  -H "Authorization: Bearer your-vcup-token" \
  -H "X-Blob-Url: https://abc123.public.blob.vercel-storage.com/screenshot-abc123.png"
```

</RequestExample>

### Error responses

| Status | Body |
|--------|------|
| 401 | `Unauthorized` |
| 400 | `Missing x-blob-url header` |
| 405 | `Method not allowed` |

---

:::endpoint GET /f/:slug
Public inline file proxy. Rewritten internally to `/api/f`.
:::

### Routing

`vercel.json` maps:

```json
{ "source": "/f/:slug*", "destination": "/api/f" }
```

The handler parses `slug` from `pathname` by stripping the `/f/` prefix. Slugs may contain path segments (e.g. nested store paths after random suffix).

### Method

Only `GET`. Other methods return **405** `Method not allowed`.

### Authentication

None. Anyone with the slug can fetch the object if the deployment has a valid `BLOB_STORE_URL`.

### Upstream resolution

The handler builds `blobUrl = `${BLOB_STORE_URL}/${slug}`` and `fetch`es it. `BLOB_STORE_URL` must be the public store base (for example `https://abc123.public.blob.vercel-storage.com`) with no trailing slash issues handled in code—use the base URL as documented in deploy steps.

### Success response

**200** with streamed body from the upstream response.

| Response header | Value |
|-----------------|-------|
| `Content-Type` | `mime-types.lookup(filename)` on the last path segment, or `application/octet-stream` |
| `Content-Disposition` | `inline` |
| `Cache-Control` | `public, max-age=31536000, immutable` |

### Streaming behavior

After a successful upstream response, the handler reads `upstream.body` with `getReader()`, writes each chunk with `res.write(value)`, then `res.end()`. If `upstream.body` is missing, it ends the response without writing bytes (**200** with empty body). This is a pull-based stream proxy, not a buffered download-then-send.

### Error responses

| Status | Body | Condition |
|--------|------|-----------|
| 400 | `Missing file slug` | Empty slug after `/f/` strip |
| 500 | `BLOB_STORE_URL not configured` | Env var unset |
| 4xx/5xx | `Not found` | Upstream `fetch` not `ok`; status code copied from upstream |
| 502 | `Failed to fetch file` | Network or fetch exception |
| 405 | `Method not allowed` | Non-GET method |

<Info>
Upstream non-OK responses propagate the upstream HTTP status to the client while always using the body text `Not found`.
</Info>

---

## CLI mapping

The published `vcup` binary in `cli.ts` is the reference client for these endpoints.

| CLI action | API call | Headers |
|------------|----------|---------|
| `vcup <file>` / stdin | `POST {VCUP_API_URL or ~/.vcuprc url}/api/upload` | `Authorization: Bearer {token}`, `X-Filename: {name}` |
| `vcup rm <url>` | `DELETE .../api/delete` | `Authorization: Bearer {token}`, `X-Blob-Url: {raw blob URL}` |

Upload reads files or stdin into memory, then emits a **64 KiB** chunked `ReadableStream` for progress display before posting. Delete accepts either a raw blob URL or a proxy URL containing `/f/`; proxy URLs require client-side `BLOB_STORE_URL` to reconstruct the blob URL before calling delete—see the upload-and-delete and troubleshooting pages.

## Server environment variables

| Variable | Used by | Role |
|----------|---------|------|
| `VCUP_TOKEN` | upload, delete | Bearer secret validation |
| `VCUP_BASE_URL` | upload | Override proxy URL host in JSON `url` field |
| `BLOB_STORE_URL` | `api/f` | Reconstruct blob URL for proxy fetches |
| `BLOB_READ_WRITE_TOKEN` | `@vercel/blob` SDK | Implicit credentials for `put` / `del` (set in Vercel project, not referenced in handler source) |

## Status code quick reference

| Code | Endpoint(s) | Meaning |
|------|-------------|---------|
| 200 | all | Success (JSON on upload; plain text on delete; bytes on GET) |
| 400 | all | Missing required header or slug |
| 401 | upload, delete | Invalid or missing Bearer token |
| 405 | all | Wrong HTTP method |
| 500 | GET `/f/...` | `BLOB_STORE_URL` missing |
| 502 | GET `/f/...` | Upstream fetch threw |
| upstream | GET `/f/...` | Blob object missing or store error |

## Related pages

<CardGroup>
<Card title="Authentication" href="/authentication">
Bearer token checks on upload and delete; public proxy route.
</Card>
<Card title="Proxy and raw URLs" href="/proxy-and-raw-urls">
`url` vs `raw` fields, `/f` rewrite, and inline disposition.
</Card>
<Card title="CLI reference" href="/cli-reference">
Headers and streaming the CLI sends to these endpoints.
</Card>
<Card title="Environment variables" href="/environment-variables">
Server and client env vars that affect API behavior.
</Card>
<Card title="Troubleshooting" href="/troubleshooting">
401, 400, 405, 500, 502, and proxy delete resolution failures.
</Card>
</CardGroup>

---

## 14. Environment variables

> Server: BLOB_READ_WRITE_TOKEN, BLOB_STORE_URL, VCUP_TOKEN, VCUP_BASE_URL. Client: VCUP_API_URL, VCUP_TOKEN, optional BLOB_STORE_URL for rm.

- Page Markdown: https://grok-wiki.com/public/docs/maxleiter-vcup-05c16ae77ebb/pages/14-environment-variables.md
- Generated: 2026-06-03T07:40:41.750Z

### Source Files

- `.env.example`
- `README.md`
- `cli.ts`
- `api/upload.ts`
- `api/f.ts`
- `api/delete.ts`

---
title: "Environment variables"
description: "Server: BLOB_READ_WRITE_TOKEN, BLOB_STORE_URL, VCUP_TOKEN, VCUP_BASE_URL. Client: VCUP_API_URL, VCUP_TOKEN, optional BLOB_STORE_URL for rm."
---

vcup splits configuration across a Vercel serverless deployment and the Bun CLI: server handlers read Blob and auth variables at runtime, while `cli.ts` resolves API endpoint and token from env or `~/.vcuprc`, with an optional client-only `BLOB_STORE_URL` when deleting by proxy URL.

## Variable map

| Variable | Runtime | Required | Used by |
| --- | --- | --- | --- |
| `BLOB_READ_WRITE_TOKEN` | Server (Vercel) | Yes | `@vercel/blob` `put` / `del` in `api/upload.ts`, `api/delete.ts` |
| `BLOB_STORE_URL` | Server | Yes for `/f` proxy | `api/f.ts` — reconstructs upstream blob URL |
| `BLOB_STORE_URL` | Client (CLI) | Only for `vcup rm` on proxy URLs | `cli.ts` — maps `/f/:slug` to raw blob URL |
| `VCUP_TOKEN` | Server | Yes for upload/delete | `api/upload.ts`, `api/delete.ts` — Bearer check |
| `VCUP_TOKEN` | Client | Yes* | `cli.ts` — `Authorization: Bearer` on upload/delete |
| `VCUP_BASE_URL` | Server | No | `api/upload.ts` — prefix for `url` in JSON response |
| `VCUP_API_URL` | Client | Yes* | `cli.ts` — base URL for `/api/upload` and `/api/delete` |
| `VCUP_API_URL` | — | — | Same value as `url` in `~/.vcuprc` |

\*Required unless `~/.vcuprc` supplies both `url` and `token`.

```text
  ┌──────────────────── Vercel deployment ────────────────────┐
  │ BLOB_READ_WRITE_TOKEN  →  @vercel/blob put/del           │
  │ BLOB_STORE_URL         →  api/f.ts fetch upstream        │
  │ VCUP_TOKEN             →  api/upload.ts, api/delete.ts   │
  │ VCUP_BASE_URL (opt.)   →  api/upload.ts response url     │
  └──────────────────────────────────────────────────────────┘
                              ▲ POST/DELETE + Bearer
                              │
  ┌──────────────────── CLI (cli.ts) ─────────────────────────┐
  │ VCUP_API_URL + VCUP_TOKEN  →  upload/delete requests     │
  │ BLOB_STORE_URL (opt.)      →  vcup rm on /f/... URLs     │
  └──────────────────────────────────────────────────────────┘
```

## Server variables

Set these on the Vercel project (or in a local `.env` for `vercel dev`). `.env.example` documents the three Blob/auth entries; `VCUP_BASE_URL` is optional and only appears in upload handler code.

### `BLOB_READ_WRITE_TOKEN`

Vercel Blob read/write credential from **Project Settings → Storage → Blob**. The handlers never read this name directly; `@vercel/blob` picks it up automatically for `put()` in `api/upload.ts` and `del()` in `api/delete.ts`.

<Note>
The one-click deploy button pre-wires `BLOB_READ_WRITE_TOKEN`, `BLOB_STORE_URL`, and `VCUP_TOKEN` in the clone URL. After deploy, confirm values in the dashboard match your Blob store.
</Note>

### `BLOB_STORE_URL`

Public base URL of the blob store (no trailing path), e.g. `https://abc123.public.blob.vercel-storage.com`.

- **`api/f.ts`**: Required. Builds `${BLOB_STORE_URL}/${slug}` to `fetch` the object. Missing config returns **500** with body `BLOB_STORE_URL not configured`.
- **Not used** by upload or delete handlers; upload returns the full `raw` URL from `put()`.

### `VCUP_TOKEN`

Shared secret for authenticated routes. Both upload and delete compare the `Authorization` header (Bearer token) to `process.env.VCUP_TOKEN`:

- Missing or empty server token → **401** `Unauthorized` for every request.
- Mismatch → **401** `Unauthorized`.

The CLI must use the **same** string in `VCUP_TOKEN` (env) or `token` in `~/.vcuprc`. Proxy GET `/f/:slug` does **not** check this token.

### `VCUP_BASE_URL`

Optional override for the **proxy** link returned from upload, not the raw blob URL.

When set, upload JSON uses:

`{ "url": "${VCUP_BASE_URL}/f/${slug}", "raw": "<blob.url from put>" }`

When unset, the handler derives the base from the incoming request:

`${x-forwarded-proto || "http"}://${host}`

Use this when the deployment hostname in responses should differ from the request host (custom domain, canonical HTTPS URL, or local dev behind a tunnel). It does not change where the CLI sends API traffic — that remains `VCUP_API_URL` / `~/.vcuprc` `url`.

## Client variables

The published CLI (`cli.ts`, bin `vcup`) runs on your machine with Bun.

### `VCUP_API_URL` and `VCUP_TOKEN`

`loadConfig()` resolution order:

1. If **both** `VCUP_API_URL` and `VCUP_TOKEN` are set → use env only (ignore `~/.vcuprc` for the missing half).
2. Else if `~/.vcuprc` exists → `url = VCUP_API_URL || rc.url`, `token = VCUP_TOKEN || rc.token`.
3. Else → exit **1** with a message to set env vars or create `~/.vcuprc`.

`VCUP_API_URL` is the deployment origin **without** a path suffix (e.g. `https://your-app.vercel.app`). The CLI appends `/api/upload` or `/api/delete`.

<ParamField body="VCUP_API_URL" type="string" required>
Deployment base URL. Equivalent to the `url` field in `~/.vcuprc`.
</ParamField>

<ParamField body="VCUP_TOKEN" type="string" required>
Must match server `VCUP_TOKEN`. Sent as `Authorization: Bearer …` on upload and delete.
</ParamField>

### `BLOB_STORE_URL` (CLI, optional)

Only required for:

```bash
vcup rm https://your-host/f/some-slug-with-suffix.png
```

If the argument contains `/f/`, the CLI extracts the slug and builds `${BLOB_STORE_URL}/${slug}` before calling `DELETE /api/delete` with header `X-Blob-Url`.

Without this variable, proxy deletes exit **1**:

`Cannot resolve proxy URL without BLOB_STORE_URL env var.`

<Tip>
Pass the **raw** blob URL from upload (`raw` field or `vcup --raw`) to delete without setting client `BLOB_STORE_URL`. The server `del()` accepts the full Vercel Blob URL directly.
</Tip>

Client `BLOB_STORE_URL` should match the server value (same public store base).

## Example layouts

<Tabs>
  <Tab title="Vercel production">
```bash
# Server (Vercel project env)
BLOB_READ_WRITE_TOKEN=vercel_blob_rw_...
BLOB_STORE_URL=https://xxxx.public.blob.vercel-storage.com
VCUP_TOKEN=your-long-random-secret
VCUP_BASE_URL=https://files.example.com   # optional
```
  </Tab>
  <Tab title="CLI shell">
```bash
export VCUP_API_URL=https://your-app.vercel.app
export VCUP_TOKEN=your-long-random-secret
export BLOB_STORE_URL=https://xxxx.public.blob.vercel-storage.com  # optional unless using vcup rm on /f/ URLs
```
  </Tab>
  <Tab title="Local vercel dev">
Copy `.env.example` to `.env` (gitignored), fill the three server vars, then `bun run dev` / `vercel dev`. Point the CLI at the dev origin with `VCUP_API_URL`.
  </Tab>
</Tabs>

## Which routes read which vars

| Route | Auth | Server env |
| --- | --- | --- |
| `POST /api/upload` | `VCUP_TOKEN` | `VCUP_TOKEN`, `BLOB_READ_WRITE_TOKEN` (SDK), optional `VCUP_BASE_URL` |
| `DELETE /api/delete` | `VCUP_TOKEN` | `VCUP_TOKEN`, `BLOB_READ_WRITE_TOKEN` (SDK) |
| `GET /f/:slug` → `api/f` | None | `BLOB_STORE_URL` |

## Failure signals

| Symptom | Likely cause |
| --- | --- |
| CLI: `Missing config` | No `VCUP_API_URL`/`VCUP_TOKEN` and no `~/.vcuprc` |
| Upload/delete **401** | `VCUP_TOKEN` mismatch or unset on server |
| Proxy **500** `BLOB_STORE_URL not configured` | Server `BLOB_STORE_URL` unset |
| Proxy **502** `Failed to fetch file` | Wrong store URL or slug; upstream fetch error |
| `vcup rm` on `/f/…` fails immediately | Client `BLOB_STORE_URL` unset |
| Proxy URL in upload JSON shows wrong host | Set `VCUP_BASE_URL` on server |

## Related pages

<CardGroup>
  <Card title="Configuration reference" href="/configuration-reference">
    ~/.vcuprc keys, precedence, and handler validation messages.
  </Card>
  <Card title="Deploy on Vercel" href="/deploy-vercel">
    Blob store creation and deploy-button env wiring.
  </Card>
  <Card title="Configure the CLI" href="/configure-cli">
    RC file vs env precedence for daily use.
  </Card>
  <Card title="Custom domain" href="/custom-domain">
    When to set VCUP_BASE_URL alongside ~/.vcuprc url.
  </Card>
  <Card title="Authentication" href="/authentication">
    Bearer token behavior on upload and delete.
  </Card>
  <Card title="Local development" href="/local-development">
    .env from .env.example and vercel dev workflow.
  </Card>
  <Card title="Troubleshooting" href="/troubleshooting">
    Status codes and config-related errors.
  </Card>
</CardGroup>

---

## 15. Troubleshooting

> Source-backed failure modes: missing config, 401/400/405/500/502 responses, directory upload rejection, proxy delete without BLOB_STORE_URL, and upstream fetch errors.

- Page Markdown: https://grok-wiki.com/public/docs/maxleiter-vcup-05c16ae77ebb/pages/15-troubleshooting.md
- Generated: 2026-06-03T07:41:10.693Z

### Source Files

- `cli.ts`
- `api/upload.ts`
- `api/delete.ts`
- `api/f.ts`
- `.env.example`

---
title: "Troubleshooting"
description: "Source-backed failure modes: missing config, 401/400/405/500/502 responses, directory upload rejection, proxy delete without BLOB_STORE_URL, and upstream fetch errors."
---

vcup surfaces failures at three boundaries: the Bun CLI (`cli.ts`) before any HTTP call, authenticated API handlers (`api/upload.ts`, `api/delete.ts`), and the public proxy (`api/f.ts`, rewritten from `/f/:slug*`). Most server errors return a plain-text body with a fixed message; the CLI prints `Upload failed:` or `Delete failed:` plus the status and response body, then exits with code `1`.

## Quick diagnosis

| Symptom | Likely layer | First check |
| --- | --- | --- |
| `Missing config` on any command | CLI | Both `VCUP_API_URL` and `VCUP_TOKEN`, or valid `~/.vcuprc` |
| `Upload failed: 401` / `Delete failed: 401` | API auth | Server `VCUP_TOKEN` matches client token; server var is set |
| `Upload failed: 400` with `Missing x-filename` | Upload API | CLI always sends `X-Filename`; custom clients must too |
| `Delete failed: 400` with `Missing x-blob-url` | Delete API | CLI sends `X-Blob-Url` with raw or resolved blob URL |
| Browser `/f/...` shows `BLOB_STORE_URL not configured` | Proxy | Deploy env `BLOB_STORE_URL` (public store URL) |
| Browser `/f/...` shows `Failed to fetch file` | Proxy upstream | Network/blob URL; store URL + slug correctness |
| `Cannot resolve proxy URL without BLOB_STORE_URL` | CLI delete | Set client `BLOB_STORE_URL` or pass raw blob URL to `vcup rm` |
| `Cannot upload a directory` | CLI | Pass a file path, not a folder |
| `File not found` | CLI | Path exists and is a file |

```text
  vcup CLI                    API (auth)                 GET /f (public)
  ---------                   ----------                 ---------------
  loadConfig()        -->     upload/delete 401/400/405
  file/dir checks     -->     (no auth on /f)
  BLOB_STORE_URL rm   -->                              f.ts 400/405/500/502
```

## CLI failures (before or after HTTP)

### Missing or incomplete config

`loadConfig()` returns immediately only when **both** `VCUP_API_URL` and `VCUP_TOKEN` are set in the environment. Otherwise it reads `~/.vcuprc` and merges: `url` from env or `rc.url`, `token` from env or `rc.token`. If the rc file is absent and the env pair is incomplete, the CLI prints a JSON example and exits:

```text
Missing config. Set VCUP_API_URL and VCUP_TOKEN env vars, or create ~/.vcuprc:
{
  "url": "https://your-vcup.vercel.app",
  "token": "your-token"
}
```

<Warning>
Setting only one of `VCUP_API_URL` or `VCUP_TOKEN` while relying on `~/.vcuprc` for the other works only when the rc file exists. With no rc file, a single env var still triggers `Missing config`.
</Warning>

Malformed `~/.vcuprc` JSON is not caught; `JSON.parse` throws and aborts the process.

### File path and stdin

| Message | Cause | Fix |
| --- | --- | --- |
| `File not found: <path>` | Resolved path does not exist | Check path; use absolute path if needed |
| `Cannot upload a directory: <path>` | `statSync` reports a directory | Upload a single file or pipe via stdin |
| Help text, exit `1` | No file args and stdin is a TTY | Pass a file, pipe stdin, or use `vcup --help` |
| `Usage: vcup rm <url>` | `vcup rm` without URL | Pass proxy or raw URL |

Stdin uploads use filename `paste.txt` and the same upload path as file uploads.

### Upload and delete HTTP errors

On non-OK responses:

```text
Upload failed: <status> <response body>
Delete failed: <status> <response body>
```

The body is whatever the API returned (plain text for built-in errors). Use the status and body string to map to the tables below.

### Proxy URL delete without `BLOB_STORE_URL`

When `vcup rm` receives a URL containing `/f/`, the CLI extracts the slug and builds `${BLOB_STORE_URL}/${slug}`. Without client env `BLOB_STORE_URL`:

```text
Cannot resolve proxy URL without BLOB_STORE_URL env var.
Pass the raw blob URL instead, or set BLOB_STORE_URL.
```

<Steps>
<Step title="Delete by raw URL">
Copy the `raw` field from upload JSON (`vcup --raw` prints it) and run `vcup rm <raw-url>`.
</Step>
<Step title="Enable proxy deletes">
Set client `BLOB_STORE_URL` to the store public URL (same value as on the Vercel project), e.g. `https://abc123.public.blob.vercel-storage.com`.
</Step>
</Steps>

## API errors (upload and delete)

Both handlers reject non-matching methods with **405** and body `Method not allowed`. Authentication uses `Authorization: Bearer <token>` compared to `process.env.VCUP_TOKEN`. If `VCUP_TOKEN` is unset on the server, every request is **401** `Unauthorized`.

### POST `/api/upload`

| Status | Body | Condition |
| --- | --- | --- |
| 405 | `Method not allowed` | Not `POST` |
| 401 | `Unauthorized` | Missing/wrong Bearer token, or server `VCUP_TOKEN` unset |
| 400 | `Missing x-filename header` | No `X-Filename` header |
| 200 | JSON `{ url, raw }` | Success |

<Note>
Upload streams the request body to `@vercel/blob` `put()` with no local try/catch. Missing or invalid `BLOB_READ_WRITE_TOKEN` on the deployment typically surfaces as a platform/runtime error rather than the messages above.
</Note>

### DELETE `/api/delete`

| Status | Body | Condition |
| --- | --- | --- |
| 405 | `Method not allowed` | Not `DELETE` |
| 401 | `Unauthorized` | Same Bearer rules as upload |
| 400 | `Missing x-blob-url header` | No `X-Blob-Url` header |
| 200 | `Deleted` | Success |

The CLI sends `X-Blob-Url` with the full Vercel Blob URL. `del(url)` is not wrapped; invalid URLs or token issues may appear as non-2xx responses or runtime errors from the Blob SDK.

## Proxy errors (`GET /f/:slug`)

`vercel.json` rewrites `/f/:slug*` to `api/f`. This route is **public** (no `VCUP_TOKEN` check). It rebuilds the blob URL as `${BLOB_STORE_URL}/${slug}` and fetches it.

| Status | Body | Condition |
| --- | --- | --- |
| 405 | `Method not allowed` | Not `GET` |
| 400 | `Missing file slug` | Empty slug after stripping `/f/` prefix |
| 500 | `BLOB_STORE_URL not configured` | `process.env.BLOB_STORE_URL` unset |
| *upstream* | `Not found` | Upstream `fetch(blobUrl)` returned `!ok`; status code copied from upstream |
| 502 | `Failed to fetch file` | `fetch` threw (network/DNS/TLS, etc.) |
| 200 | Streamed body | Success; `Content-Disposition: inline` |

<Info>
For missing blobs, the handler forwards the upstream HTTP status (often 404) with body `Not found`, not a JSON error object.
</Info>

### Upstream fetch failures

**500** means the deployment never set `BLOB_STORE_URL`. Confirm the public store URL from Vercel Storage matches `.env.example` (no trailing path beyond the store root).

**502** means the proxy could not complete `fetch` to `${storeUrl}/${slug}`. Check connectivity, that the slug matches the path segment from a successful upload `raw` URL, and that `BLOB_STORE_URL` is the store base, not a single object URL.

**Upstream non-OK** (status echoed): wrong slug, deleted object, or mismatched store URL. Compare the slug in the proxy link (`/f/<slug>`) to the pathname of the `raw` URL returned at upload time.

## Environment checklist

| Variable | Where | Symptom if wrong |
| --- | --- | --- |
| `VCUP_API_URL` / `~/.vcuprc` `url` | Client | `Missing config` or requests to wrong host |
| `VCUP_TOKEN` | Client + server | 401 on upload/delete |
| `VCUP_TOKEN` | Server only unset | All upload/delete return 401 |
| `BLOB_READ_WRITE_TOKEN` | Server (Vercel Blob) | Upload/delete blob SDK failures |
| `BLOB_STORE_URL` | Server | Proxy 500; wrong upstream 404 |
| `BLOB_STORE_URL` | Client (optional) | `vcup rm` on proxy URL fails without raw URL |
| `VCUP_BASE_URL` | Server (optional) | Upload JSON `url` uses wrong host in `url` field (not a hard error) |

## Common fix patterns

<AccordionGroup>
<Accordion title="401 on upload or delete">
Set `VCUP_TOKEN` in the Vercel project to the same secret as `token` in `~/.vcuprc` or client `VCUP_TOKEN`. Redeploy after changing server env vars.
</Accordion>
<Accordion title="Proxy link 500">
Add `BLOB_STORE_URL` on the server to the blob store public URL from the dashboard Storage tab.
</Accordion>
<Accordion title="Proxy link 502 or 404">
Verify `BLOB_STORE_URL` is the store root URL. Open the `raw` URL from upload; if that fails, the object or token is wrong. If `raw` works but `/f/` does not, slug or store URL mismatch is likely.
</Accordion>
<Accordion title="vcup rm on share link fails">
Either export client `BLOB_STORE_URL` or run `vcup rm` with the `raw` blob URL from `vcup --raw` at upload time.
</Accordion>
<Accordion title="Wrong share URL host in CLI output">
Set server `VCUP_BASE_URL` to your public origin (custom domain docs) so upload JSON `url` matches where users open `/f/...`.
</Accordion>
</AccordionGroup>

## Related pages

<CardGroup>
<Card title="Configure the CLI" href="/configure-cli">
`~/.vcuprc`, env precedence, and matching tokens to your deployment.
</Card>
<Card title="Environment variables" href="/environment-variables">
Full server and client variable reference.
</Card>
<Card title="Authentication" href="/authentication">
Bearer checks, public `/f` route, and token alignment.
</Card>
<Card title="Proxy and raw URLs" href="/proxy-and-raw-urls">
When delete and proxy serving need `BLOB_STORE_URL`.
</Card>
<Card title="API reference" href="/api-reference">
Status codes, headers, and response shapes per route.
</Card>
<Card title="Deploy on Vercel" href="/deploy-vercel">
Required env vars at first deploy.
</Card>
</CardGroup>

---

## 16. Local development

> Run vercel dev via package.json script, local .env from .env.example, and exercising upload, /f proxy, and delete against a dev deployment.

- Page Markdown: https://grok-wiki.com/public/docs/maxleiter-vcup-05c16ae77ebb/pages/16-local-development.md
- Generated: 2026-06-03T07:41:05.165Z

### Source Files

- `package.json`
- `.env.example`
- `vercel.json`
- `cli.ts`
- `api/upload.ts`
- `api/f.ts`

---
title: "Local development"
description: "Run vercel dev via package.json script, local .env from .env.example, and exercising upload, /f proxy, and delete against a dev deployment."
---

The repository’s local server entry point is the `dev` npm script, which runs `vercel dev` and serves `api/upload.ts`, `api/delete.ts`, and the `/f/:slug*` rewrite to `api/f.ts` with environment variables loaded from a gitignored `.env` file patterned on `.env.example`.

## Prerequisites

| Requirement | Role |
| --- | --- |
| [Vercel CLI](https://vercel.com/docs/cli) | Runs `vercel dev` and loads project env for serverless handlers |
| Vercel project link | `vercel dev` expects a linked Vercel project in this directory (creates `.vercel/`, gitignored) |
| Vercel Blob store | `put` / `del` in upload and delete handlers need a real `BLOB_READ_WRITE_TOKEN`; proxy serving needs `BLOB_STORE_URL` |
| Bun (recommended) | `cli.ts` is a Bun script (`#!/usr/bin/env bun`); use `bun ./cli.ts` from the repo or a global install |

<Note>
Local development still talks to a live Blob store. There is no in-memory or filesystem blob backend in this repository—copy production-like credentials into `.env`, or create a dedicated Blob store for dev.
</Note>

## Environment file

Copy the example env file and fill in values from the Vercel dashboard (**Storage** tab after deploy, or the same fields wired by the README deploy button):

```bash
cp .env.example .env
```

| Variable | Required for | Notes |
| --- | --- | --- |
| `BLOB_READ_WRITE_TOKEN` | Upload, delete | Used by `@vercel/blob` `put` / `del` (see `.env.example` comment) |
| `BLOB_STORE_URL` | `/f` proxy, CLI proxy delete | Public store URL, e.g. `https://….public.blob.vercel-storage.com` |
| `VCUP_TOKEN` | Upload, delete API | Shared secret; must match the CLI’s `token` / `VCUP_TOKEN` |
| `VCUP_BASE_URL` | Upload JSON `url` field | Optional; overrides host-derived base URL in upload responses |

`.gitignore` excludes `.env` and `.env*.local`, so secrets stay out of git.

## Start the dev server

From the repository root:

<CodeGroup>
```bash title="bun"
bun run dev
```

```bash title="npm"
npm run dev
```
</CodeGroup>

The `dev` script in `package.json` is exactly `vercel dev`. Vercel loads `.env` for server handlers and applies `vercel.json` rewrites locally:

```json
{ "source": "/f/:slug*", "destination": "/api/f" }
```

Use the origin URL that `vercel dev` prints (commonly `http://localhost:3000`) as the CLI `url` / `VCUP_API_URL`—do not assume a fixed port if your CLI shows another.

## Point the CLI at the dev server

The CLI resolves config from `VCUP_API_URL` + `VCUP_TOKEN` first, then `~/.vcuprc`. For local work, either export env vars in the shell or use a dev-only rc file.

<RequestExample>
```bash
export VCUP_API_URL="http://localhost:3000"
export VCUP_TOKEN="same-value-as-in-env"
export BLOB_STORE_URL="https://your-store.public.blob.vercel-storage.com"
```
</RequestExample>

<ResponseExample>
```json title="~/.vcuprc (alternative)"
{
  "url": "http://localhost:3000",
  "token": "same-value-as-in-env"
}
```
</ResponseExample>

<ParamField body="VCUP_API_URL" type="string" required>
Base origin of the vcup deployment (no trailing path). Uploads hit `{url}/api/upload`; deletes hit `{url}/api/delete`.
</ParamField>

<ParamField body="VCUP_TOKEN" type="string" required>
Must equal server `VCUP_TOKEN` in `.env` for Bearer auth on upload and delete.
</ParamField>

<ParamField body="BLOB_STORE_URL" type="string">
Required on the **client** when deleting by proxy URL (`/f/...`); the CLI maps slug → `{BLOB_STORE_URL}/{slug}` before calling delete.
</ParamField>

Run the CLI from the repo without publishing:

```bash
bun ./cli.ts path/to/file.png
```

## Exercise upload

<Steps>
<Step title="Start vercel dev">
Ensure `.env` has `BLOB_READ_WRITE_TOKEN`, `BLOB_STORE_URL`, and `VCUP_TOKEN`, then run `bun run dev` or `npm run dev`.
</Step>

<Step title="Upload a file">
With dev env vars set, upload a small file:

```bash
bun ./cli.ts ./README.md
```

The CLI POSTs to `/api/upload` with `Authorization: Bearer …` and `X-Filename`, streaming the body with a progress bar on stderr.
</Step>

<Step title="Verify response">
On success, stdout is one line: the proxy URL (`url`) unless `--raw` was passed (then `raw` blob URL). Upload handler shape:

```json
{ "url": "<base>/f/<slug>", "raw": "<blob-store-url>" }
```

`base` is `VCUP_BASE_URL` if set; otherwise `x-forwarded-proto` (default `http`) + `host` from the request—on local dev that is typically your `vercel dev` origin.
</Step>
</Steps>

<Warning>
Upload rejects missing `X-Filename` (400), wrong method (405), or token mismatch (401). The CLI refuses directories client-side before calling the API.
</Warning>

## Exercise the `/f` proxy

Open the printed proxy URL in a browser or fetch it:

```bash
curl -I "http://localhost:3000/f/<slug-from-upload>"
```

Flow:

1. `vercel.json` rewrites `/f/:slug*` → `/api/f`.
2. `api/f.ts` strips the `/f/` prefix, builds `{BLOB_STORE_URL}/{slug}`, fetches upstream, and streams the body with `Content-Disposition: inline` and a MIME type from the slug filename.

If `BLOB_STORE_URL` is unset server-side, the handler returns **500** with `BLOB_STORE_URL not configured`. Upstream failures yield **502**; missing slug **400**.

## Exercise delete

<Tabs>
<Tab title="Proxy URL">
```bash
export BLOB_STORE_URL="https://your-store.public.blob.vercel-storage.com"
bun ./cli.ts rm "http://localhost:3000/f/<slug>"
```

The CLI resolves `/f/` slugs using client `BLOB_STORE_URL`, then `DELETE /api/delete` with `X-Blob-Url` set to the raw blob URL.
</Tab>

<Tab title="Raw blob URL">
```bash
bun ./cli.ts rm "https://….public.blob.vercel-storage.com/<slug>"
```

No client `BLOB_STORE_URL` needed; the URL is sent as-is in `X-Blob-Url`.
</Tab>
</Tabs>

Successful delete prints `Deleted` and the API returns **200**. Auth and header errors mirror upload (401 without valid Bearer, 400 without `X-Blob-Url`).

## Local vs production behavior

```mermaid
sequenceDiagram
  participant CLI as cli.ts
  participant Dev as vercel dev
  participant Upload as api/upload.ts
  participant Blob as Vercel Blob
  participant Proxy as api/f.ts

  CLI->>Dev: POST /api/upload + Bearer + X-Filename
  Dev->>Upload: handler
  Upload->>Blob: put (addRandomSuffix)
  Upload-->>CLI: JSON url + raw
  CLI->>Dev: GET /f/slug (browser or curl)
  Dev->>Proxy: rewrite /f → /api/f
  Proxy->>Blob: fetch BLOB_STORE_URL/slug
  Proxy-->>CLI: streamed inline body
  CLI->>Dev: DELETE /api/delete + X-Blob-Url
  Dev->>Blob: del
```

| Concern | Local (`vercel dev`) | Production (Vercel deployment) |
| --- | --- | --- |
| Server env | `.env` in repo root | Vercel project environment variables |
| Proxy base in upload JSON | Request host or `VCUP_BASE_URL` | Deployment host, custom domain, or `VCUP_BASE_URL` |
| Blob storage | Same remote store unless you use a separate dev store | Project-linked Blob store |
| CLI target | `VCUP_API_URL` / `~/.vcuprc` `url` → dev origin | Production deployment URL |

<Info>
Set `VCUP_BASE_URL` in `.env` when proxy links must always use a specific origin (for example a stable tunnel URL or custom domain) instead of whatever `host` header `vercel dev` sends.
</Info>

## Quick verification checklist

| Step | Command / action | Expected signal |
| --- | --- | --- |
| Server up | `bun run dev` | Vercel dev ready message with local URL |
| Upload | `bun ./cli.ts <file>` | One-line `http://…/f/…` URL on stdout |
| Proxy | Open that URL | File content inline; correct `Content-Type` when extension is known |
| Delete | `bun ./cli.ts rm <url>` | `Deleted`; blob removed (re-fetch proxy → 404) |
| Auth | Mismatch `VCUP_TOKEN` | `Upload failed: 401` or delete 401 |

## Common local issues

| Symptom | Likely cause |
| --- | --- |
| `Missing config` from CLI | No `VCUP_API_URL`/`VCUP_TOKEN` and no `~/.vcuprc` |
| Upload `401 Unauthorized` | CLI token ≠ `.env` `VCUP_TOKEN` |
| Proxy `500 BLOB_STORE_URL not configured` | `.env` missing `BLOB_STORE_URL` on server |
| `Cannot resolve proxy URL without BLOB_STORE_URL` on `vcup rm` | Client env missing `BLOB_STORE_URL` when deleting `/f/` URLs |
| Upload succeeds but `/f` 404 | Slug/store mismatch; confirm `BLOB_STORE_URL` matches the store used by `put` |
| `502 Failed to fetch file` | Wrong store URL or blob not public upstream |

See the troubleshooting page for status-code detail across handlers.

## Related pages

<CardGroup>
<Card title="Deploy on Vercel" href="/deploy-vercel">
Create the Blob store and obtain `BLOB_READ_WRITE_TOKEN` and store URL for `.env`.
</Card>
<Card title="Environment variables" href="/environment-variables">
Server and client variable reference including optional `VCUP_BASE_URL`.
</Card>
<Card title="Configure the CLI" href="/configure-cli">
`~/.vcuprc` schema and env precedence for targeting a dev origin.
</Card>
<Card title="Upload and delete" href="/upload-and-delete">
CLI upload streaming, `--raw`, and delete URL forms in depth.
</Card>
<Card title="API reference" href="/api-reference">
Handler methods, headers, and response codes for `/api/upload`, `/api/delete`, and `/f`.
</Card>
<Card title="Troubleshooting" href="/troubleshooting">
Source-backed failure modes for auth, proxy, and delete paths.
</Card>
</CardGroup>

---