# 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.

- Repository: MaxLeiter/vcup
- GitHub: https://github.com/MaxLeiter/vcup
- Human docs: https://grok-wiki.com/public/docs/maxleiter-vcup-05c16ae77ebb
- Complete Markdown: https://grok-wiki.com/public/docs/maxleiter-vcup-05c16ae77ebb/llms-full.txt

## 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>
