---
title: Screenshots → mockups
description: Turn a screenshot your coding agent just captured into a finished product mockup, without the image ever entering the model's context.
---

The most common reason to connect Brand Stack to a coding agent: you're
building a feature, the agent takes a screenshot to check its work, and
you want that screenshot on a device, in a scene, ready for the landing
page.

```text
Take a screenshot of the dashboard we just built, then turn it into a
MacBook Pro mockup with Brand Stack and save it to ./marketing/hero.png.
```

## No shell? Still works

The upload command needs a shell, and not every client has one. Two
tools cover chat-only clients (Claude.ai, Claude Cowork, mobile):

- **A pasted URL** → the agent calls `capture_screenshot`, and Brand
  Stack's own server renders the page and stores the capture (desktop
  1440×900 or mobile 390×844 viewport, matched to the device frame you
  want). Paste a link in Cowork, ask for a mockup, done. No browser,
  no upload step. Public pages only: a local dev server is only
  reachable from your machine, so that stays the CLI's job.
- **A pasted image** → the agent calls `create_upload_link` and hands
  you a 30-minute link. Drop the same image there from your browser,
  no login needed, tell the agent it's up, and it picks the file out
  of your Screens timeline.

Every screenshot that arrives this way lands on the workspace's
**Screens** page: a chronological timeline, labeled with the brand and
product it belongs to, its device shape, and (for captures) the page
URL. Agents browse it with `list_screenshots`, and can attribute a
screenshot to a specific brand with the `brand` parameter on
`capture_screenshot`, the `x-brand` header on uploads, or `--brand` on
the CLI — omitted, the workspace's default brand applies.

## What the agent works out on its own

You shouldn't have to configure a mockup from inside a chat. The tools
tell the agent to infer everything the context already answers, and to
ask at most one short question:

- **Device, from the screenshot's shape.** `brandstack upload` prints
  the image's width×height alongside the URL. A portrait capture is a
  phone: the agent picks the iPhone frame without asking. A landscape
  capture is a desktop app, which is the one question worth asking:
  MacBook Pro or Apple Studio Display.
- **Persona, from your coding session.** The agent knows which product
  it's working on. It matches that against your workspace's products
  and offers the matching personas by name, or leaves the persona out,
  since it's optional.
- **Everything else defaults.** Aspect ratio 16:9 unless the image is
  for a feed post (1:1) or a story (9:16); camera and lighting left to
  the image model.

Say "just make it a mockup" and the agent skips the question too.

The exception is your **first mockup**. When the workspace has never
generated one, the agent slows down and offers real choices in a single
message: the camera angle (straight on, over the shoulder, a macro
close-up, or a high angle shot), the aspect ratio, and the image size,
with each size tier's actual credit cost and your balance. One guided
pass shows you what you can ask for; every mockup after that goes back
to inference.

## Credits stay visible

Generation costs credits, and the agent is instructed to keep that
visible rather than silently spending: it checks `get_credits` before a
session's first generation, quotes the cost of the tier you picked
(higher resolutions cost more, and the prices come from your workspace,
not a guess), and every generation returns the remaining balance. When
it runs low, the agent tells you and links your subscription page,
where you upgrade or buy a credit pack in the browser. Every finished
image also comes back with a `viewUrl` link that opens it in your
workspace, so nothing gets lost in the chat scrollback.

## Your first 10 mockups are free

A newly connected workspace gets credits for **10 free mockups** on its
first tool call, so you can feel the flow out before anything costs
money. The grant lands in your normal credit balance: `get_credits`
shows it, and the [studio at brandstack.dev](https://www.brandstack.dev)
can spend it too.

## Why the upload step exists

Every image parameter on `create_mockup` and `create_scene` is a **URL
that Brand Stack's server fetches**. That's deliberate: it's how the
image model receives reference imagery. It also means a file on your
machine is not something the tool can read: `file://` URLs, `localhost`,
and private network addresses are all refused, since a server that
fetches arbitrary caller-supplied addresses is an SSRF hole.

Carrying the bytes through the MCP call itself is possible but
expensive: tool arguments are JSON inside the model's context window,
and a base64-encoded screenshot there costs roughly 35K tokens per
100KB, for an image the model never needs to look at.

So the bytes take the short path by default: your agent's shell posts
them straight to your workspace, and only the URL comes back. The
inline route (`upload_screenshot`) exists for the one situation where
the short path is closed — see
[sandboxed environments](#sandboxed-environments-when-the-post-is-blocked) below.

## The flow

No setup beyond the connector. The agent captures the screenshot
however it already does, then moves the file through an upload link it
mints itself:

```bash
# 1. The agent captures a screenshot however it already does
npx playwright screenshot http://localhost:3000/dashboard ./shot.png

# 2. The agent calls the create_upload_link tool, which returns an
#    uploadEndpoint, and posts the file to it. No API key, no credits.
#    The response carries the hosted url plus width and height.
curl -X POST -H "content-type: image/png" \
  --data-binary @shot.png "$UPLOAD_ENDPOINT"
# {"url":"https://…/shot.png","width":1440,"height":900,…}

# 3. The agent generates, with the screenshot on a device
#    (the create_mockup tool, passing the url as screenshot_url)
```

### With an API key: the same flow, one command shorter

If you keep an [API key](/mcp/api-keys) in the agent's environment
(`npx @brandstack/cli login` once, or `BRANDSTACK_API_KEY`), the CLI
collapses the upload and can even generate from the terminal directly.
Optional, and mostly interesting for CI and scripting:

```bash
npx @brandstack/cli upload ./shot.png
# https://…public.blob.vercel-storage.com/…/shot.png

npx @brandstack/cli create-mockup \
  --screenshot-url "https://…/shot.png" \
  --device-type macbook_pro \
  --atmosphere "Studio Lighting" \
  --out ./marketing/hero.png
```

Step 2 is optional, because `create-mockup` and `create-scene` accept a
**local path** in any `--*-url` flag and upload it for you:

```bash
npx @brandstack/cli create-mockup \
  --screenshot-url ./shot.png \
  --device-type macbook_pro \
  --out ./marketing/hero.png
```

The CLI uploads `./shot.png` first, tells you what it did on stderr, and
passes the resulting URL to the tool.

## Sandboxed environments: when the POST is blocked

Some agent runtimes route MCP traffic and general HTTP through
**different proxies**. Claude Code on the web is the common case: MCP
tool calls go through a dedicated channel that always works, while
`curl` and `fetch` go through an egress proxy that only allows
allowlisted domains. If brandstack.dev is not on that allowlist, the
POST to `uploadEndpoint` fails with a 403 policy error even though
every Brand Stack tool call succeeds.

Two ways out:

1. **Allowlist brandstack.dev** in the environment's network settings
   (the lasting fix, when you control the environment).
2. **Fall back to `upload_screenshot`**, which carries the bytes
   base64-encoded inside the tool call itself:

```bash
# Compress first: every kilobyte of base64 costs context tokens.
# A 1440x900 UI screenshot re-encoded as an ~80-quality JPEG is
# typically well under 200KB.
base64 -w0 shot.jpg   # the string to pass as data_base64
```

The tool detects the image type from the bytes, stores the screenshot
in the Screens timeline like every other route, and returns the hosted
`url` plus `width` and `height`. Decoded limit: 3MB (the POST route
takes 5MB). Agents are told about the fallback in the tool
descriptions, so in practice they recover from the 403 on their own.

## Using the MCP connector instead

If you're driving generation through the MCP connector rather than the
CLI, the upload still happens through the CLI, since it's the only half of
the pair with access to your filesystem. Agents are told this in the
tool descriptions, so in practice the agent runs `npx @brandstack/cli
upload ./shot.png` in its shell and passes the URL to `create_mockup`
over MCP without being asked.

Any public https URL works just as well, so an image already hosted
somewhere (your CDN, a public bucket) needs no upload at all.

## Uploading directly

`upload` is a thin wrapper over one endpoint, if you'd rather call it
yourself:

```bash
curl -X POST https://www.brandstack.dev/api/uploads \
  -H "Authorization: Bearer $BRANDSTACK_API_KEY" \
  -H "Content-Type: image/png" \
  -H "X-Filename: shot.png" \
  --data-binary @shot.png
```

```json
{
  "url": "https://…/shot.png",
  "assetId": "…",
  "filename": "shot.png",
  "bytes": 184320
}
```

| | |
|---|---|
| **Auth** | Same bearer credentials as `/api/mcp` (an [API key](/mcp/api-keys) or an OAuth token), with the `mcp:write` scope. |
| **Types** | PNG, JPEG, WebP, GIF. |
| **Size** | 5MB, matching what the generator will fetch. A larger file is refused here rather than accepted and silently dropped later. |
| **Credits** | None. Uploading is free; only generation costs credits. |
| **Result** | The image lands in your Screens timeline — the **Screens** page in the workspace sidebar — labeled with its brand, device, and page URL, so `list_screenshots` and the timeline both show it. Attach metadata with optional headers: `x-brand` (a brand stack UUID, or `none`), `x-device` (`mobile`, `laptop`, `desktop`), `x-source-url` (the page the screenshot shows). |

## Troubleshooting

**"must be a public http(s) URL that this server can fetch"**: a local
path, `file://` URL, or `data:` URI reached a generation tool. Upload it
first and pass the URL.

**413 or "the limit is 5MB"**: re-capture at a lower device scale
factor, or crop to the region you actually want on screen. Full-page
screenshots at 2× cross 5MB easily.

**403**: the key is read-scope. Uploading writes to your workspace, so
it needs `mcp:write`. Issue a new key in **Settings → Developer**.
