# appShots documentation

> App Store screenshots for iPhone, iPad and Mac, made by your coding agent over MCP or by hand in the Studio. Device frames, headlines, badges, and every size App Store Connect asks for.

---

# appShots documentation

Source: https://shots.beffa.xyz/docs

appShots makes App Store screenshots. You give it the raw screens of your app. It puts them in device frames with a headline, a background and badges, and exports PNG files at the exact sizes App Store Connect accepts.

You can build a set by hand in the Studio, the editor in your browser, or let a coding agent such as Claude Code or Codex make the whole set while you watch.

## Start here

- **[Quick start](/docs/quick-start)**: your first set in about ten minutes.
- **[Connect an agent](/docs/connect)**: one command sets up Claude Code, Codex, OpenCode, Pi or another agent.
- **[Update](/docs/update)**: what updates by itself, and the one command for the rest.

## The four parts

| Part | Where it runs | What it does |
| --- | --- | --- |
| Studio | Your browser, at https://shots.beffa.xyz | Shows every slide as a live preview at export size. You edit copy, layout and look, and download the set as a ZIP. |
| MCP server | https://shots.beffa.xyz/mcp | Ten tools that let an agent create projects, write slides and render them. |
| `appshots` command | Your Mac | Runs your app in the simulator, captures screens from the simulator, a plugged-in iPhone or iPad, the window of a Mac app or the pages of a web app, and uploads them. |
| `appshots` skill | Your agent | The instructions for the whole job: ask a few questions, get the screens, write the headlines, build the set, check the render. |

The server draws the pictures. It cannot see your Mac, your simulator or your phone, which is why capturing happens on your machine and the result is uploaded.

## What it makes

- Portrait screenshots for every iPhone, iPhone Duo and iPad display App Store Connect lists, up to ten per set. You design once per device and get every size. See [Output sizes](/docs/sizes).
- Landscape screenshots for the Mac App Store, with the app as a window or inside a MacBook. See [Mac apps](/docs/mac).
- Sets for apps that are not Xcode projects: Flutter, React Native, Expo, Capacitor, Tauri, Electron and web apps. See [Web apps and other toolkits](/docs/web-apps).
- The wide images for the Header and Search Results tab of App Store Connect. See [Header and search results](/docs/store-assets).
- Any number of languages from one set. See [Languages](/docs/languages).
- Mockups: each screenshot in its device on a transparent background, for a website or a README. See [Mockups](/docs/mockups).
- Banners: the wide picture at the top of a README, the preview a link shows, a picture for a post. See [Banners](/docs/banners).

## What it does not make

- Landscape screenshots of an iPhone or iPad app, or screenshots for Apple Watch, Apple TV or Vision Pro.
- App preview videos.
- Anything for Google Play. The sizes and the device frames are Apple's.

## All pages

**Make a set:** [The skill](/docs/skill) · [Get your screens in](/docs/screens) · [Mac apps](/docs/mac) · [Web apps and other toolkits](/docs/web-apps) · [The Studio](/docs/studio) · [Logos and artwork](/docs/images) · [Languages](/docs/languages) · [Render and export](/docs/export) · [Mockups](/docs/mockups) · [Banners](/docs/banners) · [What App Review accepts](/docs/app-review)

**Reference:** [Slides](/docs/slides) · [Header and search results](/docs/store-assets) · [Output sizes](/docs/sizes) · [The appshots command](/docs/cli) · [MCP tools](/docs/mcp) · [REST API](/docs/api)

**Help:** [Accounts, tokens and limits](/docs/accounts) · [Troubleshooting](/docs/troubleshooting) · [Questions and answers](/docs/faq)

**Legal:** [Privacy policy](/privacy) · [Terms of use](/terms)

## For agents reading this

Every page is also plain Markdown: add `.md` to its address, as in `https://shots.beffa.xyz/docs/quick-start.md`. [`/llms.txt`](/llms.txt) lists all pages, [`/llms-full.txt`](/llms-full.txt) is all of them in one file, and [`/skill.md`](/skill.md) is the skill an agent follows to make a set.

---

# Quick start

Source: https://shots.beffa.xyz/docs/quick-start

There are two ways to a finished set. Both end in the same place: a project in the Studio that you can still edit by hand, and a ZIP of PNG files for App Store Connect.

## What you need

- A Google account, to sign in.
- **With an agent:** a Mac with [Node.js](https://nodejs.org) 18 or newer and a coding agent (Claude Code, Codex, OpenCode, Pi or another). An iPhone or iPad app needs Xcode as well. A [web app](/docs/web-apps) needs Chrome, Edge or Brave instead.
- **By hand:** a browser and screenshots of your app.

## With an agent

1. Open [the Studio](/studio) and sign in with Google.
2. Click **Connect an agent** in the sidebar. Pick your agent, then **Do everything for me**, then **Create token**.
3. Copy the command that appears and paste it into Terminal. It looks like this:

   ```bash
   curl -fsS https://shots.beffa.xyz/install.sh | APPSHOTS_TOKEN=<your token> sh -s claude
   ```

4. Open your agent in your app's folder, in a new session, and start the skill:

   | Agent | How to start |
   | --- | --- |
   | Claude Code | `/appshots` |
   | Codex | `$appshots` |
   | OpenCode | Ask: "Make my App Store screenshots" |
   | Pi | `/skill:appshots` |
   | Another agent | "Read `~/.agents/skills/appshots/SKILL.md` and follow it" |

5. Answer its questions: iPhone or iPad, where the screens come from, light or dark, and any real rating or award to show.

The agent gets the screens, writes the headlines, builds the set and renders it. The set shows up in the Studio as it goes. Expect about ten minutes when you open the screens yourself, and about twenty when the agent reaches them on its own.

[Connect an agent](/docs/connect) explains what the command sets up, and [The skill](/docs/skill) what the agent does step by step.

## By hand

1. Open [the Studio](/studio) and sign in with Google.
2. Click **New project** and name it after your app.
3. Drag your screenshots from Finder onto the page. In the iOS Simulator, ⌘S saves the current screen to the desktop.
4. Click a slide, pick a layout, type the headline, and click a screenshot to put it in the slide. **+ Add slide** adds the next one.
5. Set the look once for the whole set under the **Project** tab: background, font and colours.
6. Click **Export ZIP**.

[The Studio](/docs/studio) covers every control.

## Then

- Upload the files to App Store Connect. See [Render and export](/docs/export).
- Read [What App Review accepts](/docs/app-review) before you put ratings, awards or logos on a slide.

---

# Connect an agent

Source: https://shots.beffa.xyz/docs/connect

An agent needs three things to make a set on its own: the **skill** (what to do), the **`appshots` command** (to run your app and capture its screens) and a **connection to the server** (to build and render the set). One command installs all three.

## The one command

1. Open [the Studio](/studio), sign in, and click **Connect an agent** in the sidebar.
2. Pick your agent, then **Do everything for me**.
3. Click **Create token**. The command appears with your token in it.
4. Paste it into Terminal on your Mac.

```bash
curl -fsS https://shots.beffa.xyz/install.sh | APPSHOTS_TOKEN=<your token> sh -s <agent>
```

`<agent>` is `claude`, `codex`, `opencode`, `pi` or `other`. Left out, it is `claude`. The command needs [Node.js](https://nodejs.org) 18 or newer and does four things:

1. Checks the token against the server and saves it in `~/.config/appshots/config.json`, readable by you only.
2. Puts the `appshots` command next to the skill and links it into `~/.local/bin`, `~/bin` or the folder Claude Code sits in, whichever is on your `PATH`.
3. Installs the skill where your agent looks for skills.
4. Connects the agent to the server's MCP endpoint for every folder, replacing an older `appshots` entry.

Then open the agent in your app's folder, **in a new session** so that it sees the skill, and start it.

## What goes where

| Agent | The skill is installed in | Start it with | MCP connection |
| --- | --- | --- | --- |
| Claude Code | `~/.claude/skills/appshots` | `/appshots` | `claude mcp add`, for every folder |
| Codex | `~/.agents/skills/appshots` | `$appshots` | `codex mcp add`, token in `~/.codex/config.toml` |
| OpenCode | `~/.agents/skills/appshots` | Ask for the screenshots | `opencode mcp add --global` |
| Pi | `~/.agents/skills/appshots` | `/skill:appshots` | None. Pi has no MCP, so the skill uses the command |
| Another agent | `~/.agents/skills/appshots` | "Read `~/.agents/skills/appshots/SKILL.md` and follow it" | By hand, if the agent has MCP |

Using several agents? Run the command once per agent. A copy that was set up for another agent is brought up to date at the same time.

## Three ways to work

**Connect an agent** offers three, for every agent:

| Way | What you do | What gets set up |
| --- | --- | --- |
| Do everything for me | Paste one command, then start the skill in your app's folder | The skill, the command and the MCP connection |
| Tell it what to do | Paste one command, add your screenshots, describe the set in a sentence | The MCP connection only |
| Make it by hand | Nothing | Nothing. You work in the Studio |

## Only the MCP connection

If you add the screenshots yourself and only want the agent to design the set, the connection is enough.

Claude Code:

```bash
claude mcp add --transport http --scope user appshots https://shots.beffa.xyz/mcp --header "Authorization: Bearer <your token>"
```

OpenCode:

```bash
opencode mcp add --global --url https://shots.beffa.xyz/mcp --header "Authorization=Bearer <your token>" appshots
```

Codex takes no header on its command line. Use the one command above, or add the server yourself in `~/.codex/config.toml`:

```toml
[mcp_servers.appshots]
url = "https://shots.beffa.xyz/mcp"
http_headers = { Authorization = "Bearer <your token>" }
```

Any other client works if it speaks Streamable HTTP and can send a bearer header. The endpoint does not do OAuth. Then ask in plain words:

> Make App Store screenshots for my habit tracker in project "streaks". Six slides, dark purple look. Open with a hero slide, then one slide per feature using the screenshots home, stats and widgets.

[MCP tools](/docs/mcp) lists what the agent can call.

## Without MCP

An agent that can run commands but has no MCP reaches the same tools through the command:

```bash
appshots tools
appshots call create_project '{"name": "Streaks"}'
appshots call render '{"project": "streaks"}'
```

The skill does this by itself when the session has no working `appshots` MCP server. See [The appshots command](/docs/cli).

## Check that it works

```bash
appshots check
```

It reports whether the server answers and accepts your token, whether the skill is current, which Xcode there is, the project in the current folder and what it is made with, the simulators it would use and any plugged-in device. Each line says what to do when something is missing.

## Tokens

An agent cannot sign in with Google, so it uses a token of yours. A token reaches your projects and nothing else: it cannot create more tokens, change your account or see anybody else's work.

- A token is shown once, when it is created. Lost it? Create a new one and run the command again.
- **Settings** in the Studio's sidebar lists your tokens with the day each was last used, and revokes them.
- After revoking or replacing a token, run the one command again with the new one.

## Disconnect

1. Revoke the token under **Settings**. Nothing on your Mac can reach your projects after that.
2. Remove the agent's entry: `claude mcp remove appshots --scope user`, `codex mcp remove appshots`, or the `appshots` entry in your agent's MCP settings.
3. Delete the skill folder from the table above, the link named `appshots` on your `PATH`, and the folder `~/.config/appshots`.

---

# Update

Source: https://shots.beffa.xyz/docs/update

appShots has a part that lives on the server and a part that lives on your Mac. Only the second one needs you.

## What updates by itself

Everything on the server: the Studio, the layouts, fonts and badges, the MCP tools, and the design guide an agent reads before it builds a set. A new version is live for everybody as soon as it is deployed.

- If the Studio is open in a tab, reload it to get the new version.
- Your projects, screenshots and renders stay as they are. A project is drawn by the current version each time it is rendered, so **render again** to pick up a fix or a redrawn layout.
- A connected agent needs nothing. It asks the server for its tools and the guide every time.

## What lives on your Mac

Two files, installed by the [setup command](/docs/connect): the skill (`SKILL.md`) and the `appshots` command (`appshots.mjs`). The server hands out the versions it was deployed with. Bring your copies up to date with:

```bash
appshots update
```

It needs no token and leaves your agent's connection alone. If you set up several agents, all of their copies are updated.

## How you find out

```bash
appshots check
```

The `Skill:` line says whether the server has a newer one. You rarely have to look:

- The skill starts every job with `appshots check`, and when the server has a newer skill, the agent runs `appshots update` and reads the new instructions before going on.
- The command says so by itself. After a command such as `appshots upload` or `appshots pull` it prints one line when your copies are behind the server's. It asks the server for that at most twice a day, and stays quiet when the server cannot be reached.

A copy from before this notice existed learns it with its next `appshots update`.

## When to run the setup command again

- **You replaced or revoked your token.** Create a new one under **Connect an agent** and paste the new command.
- **You want to add another agent.** Run it with that agent's name at the end.
- **`appshots update` is not a command your copy knows.** It is from before updates existed. Run the setup command once more.
- **The agent says the MCP server answers 401.** The token it was connected with no longer works. The skill carries on through the command, and running the setup command again repairs the connection.

## A new Mac

Nothing moves with you except your account. Sign in, create a token under **Connect an agent** and run the command on the new Mac. Your projects are on the server.

---

# The appshots skill

Source: https://shots.beffa.xyz/docs/skill

The skill is a page of instructions your agent follows to turn the app in the current folder into a finished set. It works the same way every time, which is what keeps a set to ten or twenty minutes. You can read it at [`/skill.md`](/skill.md).

## Start it

Open the agent in your app's repository, in a new session, then:

| Agent | How to start |
| --- | --- |
| Claude Code | `/appshots` |
| Codex | `$appshots` |
| OpenCode | Ask: "Make my App Store screenshots" |
| Pi | `/skill:appshots` |
| Another agent | "Read `~/.agents/skills/appshots/SKILL.md` and follow it" |

Or just ask for App Store screenshots. An agent that has the skill installed picks it up. Not installed yet? See [Connect an agent](/docs/connect).

The same skill makes [mockups](/docs/mockups): ask for "iPhone mockups of the app for the website" or "a transparent picture of the home screen for the README", and the agent gets the screens, draws each one in its device on a transparent background and puts the files where you want them. It asks no questions for that and writes no headlines.

It also makes [banners](/docs/banners): ask for "a banner for the README" and the agent builds it from the project's screenshots, renders it and puts the file into the repository.

**A Mac app** takes a route of its own, which the agent picks when it reads the project: it builds and opens the app on your Mac and captures its window, instead of using the simulator. See [Mac apps](/docs/mac).

**An app that is not an Xcode project** works too. The agent reads what the project is made with, starts it with the toolkit's own command and captures it where it runs: a Flutter, React Native, Expo or Capacitor app in the simulator, a Tauri or Electron app in its window, and a web app page by page in a browser. See [Web apps and other toolkits](/docs/web-apps).

## What the agent does

1. **Checks the setup** with `appshots check`: the server, the token, Xcode, the project and what it is made with, simulators and devices. If the server has a newer skill, it updates itself first.
2. **Asks you a few questions**, all at once. They are listed below.
3. **Reads a little of your code**: the README, the root view, the accent colour. It wants one sentence on what the app does and the four to seven things worth showing.
4. **Plans five to eight slides** and shows you the list: slide, screen, headline. The first slide sells the whole app, and the rest take one benefit each.
5. **Creates the project**, or continues the one your app has already.
6. **Gets the screens**, the way you chose. See [Get your screens in](/docs/screens).
7. **Writes the copy**: headlines of two to five words, one benefit each.
8. **Builds and renders** the set, looks at the result, and fixes single slides. Two rounds at most. When you asked for more than one device, each gets a set of its own: the same story, composed again for the iPad's or the Mac's canvas. See [A set for each device](/docs/slides#a-set-for-each-device).
9. **Hands over**: the link to the project in the Studio, how to get the files, and what it assumed or left out.

## The questions it asks

| Question | Your choices |
| --- | --- |
| Devices | iPhone only, or the iPad or the iPhone Duo as well. For an app that also runs on the Mac, the Mac as well. For a web app, whether to show it on an iPhone, an iPad, a Mac or several. Never about sizes: one design covers every size of a device |
| The screens | You have screenshots. You open each screen and it captures. Or it does everything itself |
| Where the app runs | A simulator, or the plugged-in iPhone or iPad. Not asked for a Mac app or a web app |
| The look | Light or dark, and which languages |
| Extras | A real rating, download count, award or review. Logos of services the app works with. Your own logo or artwork. Header and search results images |

Say "decide yourself" and it takes: iPhone only, does everything itself without changing your app, the appearance the app looks best in, the main language, and no badges, logos, artwork or header images.

**"You open each screen" is the quickest way to real content.** The agent starts the app and gives you one command to paste. You open each screen and press Return.

## What it will not do

- **Invent figures.** Badges, a slide of figures and a slide of reviews show only the ratings, download counts, awards and reviews you gave it. Invented ones get an app rejected.
- **Draw, generate or download artwork.** It uses the files you give it or the ones in your repository.
- **Write scripts or UI tests** to take screenshots. It uses the `appshots` command for everything on the Mac.
- **Change your app without asking.** Reaching a screen sometimes needs a few lines that only exist in debug builds. It asks first and tells you which file it changed.
- **Make videos.** appShots makes still images.

## Get a better set

- Give it the real numbers up front: "4.8 stars from 2,100 ratings, 50K downloads".
- Name the screens that matter: "the stats screen is the point of the app".
- Put believable content in the app first. Empty lists make empty screenshots.
- Ask for changes in plain words afterwards: "slide 3: move the phone down a little and shorten the headline". The agent changes that slide and renders only it.
- Or finish in [the Studio](/docs/studio). Every slide the agent made can be edited by hand.

## Keep it current

The skill checks for a newer version of itself at the start of every job. To do it by hand, run `appshots update`. See [Update](/docs/update).

---

# Get your screens in

Source: https://shots.beffa.xyz/docs/screens

A slide shows a raw screenshot of your app inside a device frame. Raw means the plain screen, as the simulator or the phone saves it, with nothing around it.

## Names

Every screenshot has a short name, such as `home`, `stats` or `detail`. Slides refer to screenshots by name.

- Uploading under a name that exists **replaces** that screenshot, and every slide that uses it updates.
- iPhone, iPhone Duo, iPad and Mac are told apart by the size and shape of the image. **One name can hold one of each**, and each output size picks the matching one. See [Sizes](/docs/sizes#which-device-a-screenshot-counts-as).
- Names are lowercase letters, digits, `-` and `_`.

## In the browser

Drag the files from Finder onto the project page in the Studio, or use **Upload** under **Assets**. The name is the file name. In the iOS Simulator, ⌘S saves the current screen to the desktop.

## From files you have

```bash
appshots upload streaks ./screens
appshots upload streaks home.png stats.png
appshots upload streaks IMG_0412.png --name home
```

`streaks` is the project. A folder stands for every PNG and JPEG in it.

## You open each screen

The quickest way to screens with real content. Start the app in the simulator, or open it on a plugged-in, unlocked iPhone or iPad, then:

```bash
appshots shoot streaks home stats detail settings --clean
```

It names each screen in turn and waits for Return while you open it, then captures and uploads. `s` skips one, `q` stops. Add `--device` for the plugged-in device.

One at a time works too:

```bash
appshots capture --name home --project streaks --clean
appshots capture --name home --project streaks --device
```

## The agent runs the app

With [the skill](/docs/skill), the agent builds your app once and starts it on each screen:

```bash
appshots run
appshots launch com.example.streaks --capture stats --to streaks -- -appshots stats
```

`run` finds the Xcode project and scheme, boots the simulator the store sizes ask for (an iPhone Pro Max, the 13-inch iPad Pro with `--ipad`, or the iPhone Duo with `--duo`), and reuses the build Xcode already made. `launch` starts the installed app again with launch arguments, waits two seconds, captures with a clean status bar and uploads.

To land on a screen, the agent uses what your app has already: launch arguments, a demo mode or deep links. If there are none and you agree, it adds a launch argument that only exists in debug builds. Otherwise it asks you to open the screen.

An app made with Flutter, React Native, Expo, Capacitor or Tauri is started by its own run command, after `appshots boot` has booted the simulator for it. Capturing works the same. See [Web apps and other toolkits](/docs/web-apps#an-iphone-or-ipad-app-from-another-toolkit).

## A Mac app

A Mac app runs on the Mac itself. Open it on the screen to show, then capture its window:

```bash
appshots capture --mac Ledger --name overview --project ledger
appshots shoot ledger overview budgets reports --mac Ledger
```

`Ledger` is the app's name. The terminal needs the Screen Recording permission for this. See [Mac apps](/docs/mac).

## A web app

A web app is captured page by page, by address, at the size of an iPhone, an iPad or a Mac:

```bash
appshots capture --web http://localhost:3000/stats --name stats --project streaks
appshots capture --web http://localhost:3000/stats --name stats --project streaks --as mac
```

The page is drawn by the Chrome, Edge or Brave you have, without opening a window. Options set the colour scheme, the language, sample data and the click that opens a screen. See [Web apps and other toolkits](/docs/web-apps).

## A clean status bar

`--clean` sets the simulator's status bar to 9:41 with full battery and signal before capturing. It works on simulators only. On a real device the status bar is whatever the device shows.

## Light, dark and languages

```bash
appshots run --dark --lang de
appshots launch com.example.streaks --light --lang pt-BR
```

`--dark` and `--light` set the simulator's appearance. `--lang` starts the app in that language.

A project in several languages holds each screen once per language, under the same name. Add `--lang` to the capture and the screenshot goes to that language:

```bash
appshots launch com.example.streaks --lang de-DE --capture home --to streaks -- -appshots home
```

A language shows the first language's screenshots until it has its own. See [Languages](/docs/languages#screenshots-per-language).

## What you need on the Mac

| For | You need |
| --- | --- |
| The command itself | Node.js 18 or newer |
| Simulator capture | Xcode with an iOS simulator |
| Device capture | Xcode 27 or newer, and the device plugged in, unlocked and trusting the Mac |
| Mac window capture | The Screen Recording permission for the terminal, under System Settings → Privacy & Security |
| Web page capture | Google Chrome, Chromium, Microsoft Edge or Brave. No Xcode |

If `xcode-select` points at the Command Line Tools, the command switches to `/Applications/Xcode.app` by itself.

## What makes a good screen

- Believable content. No empty states.
- No onboarding, permission prompt, paywall, keyboard, spinner, alert or debug banner.
- Nothing private: no real names, addresses or messages.
- One appearance, light or dark, across the set, unless a slide is about the other one.

## Limits

- PNG, JPEG or WebP, up to 40 MB a file and at least 300 pixels on the short side.
- 30 screenshots per project, and as many again for each further language.
- Captures are also saved in `./raw-shots`, in the folder you ran the command in. Do not commit that folder.

---

# Mac apps

Source: https://shots.beffa.xyz/docs/mac

appShots makes the screenshots of a macOS app the same way as those of an iPhone app. The Mac is one more device beside the iPhone, the iPhone Duo and the iPad: it has its own screenshots, its own preview in the Studio and its own folder in the export. The pictures are landscape.

An app that runs on the iPhone and the Mac keeps both in one project.

## What the Mac App Store takes

One to ten landscape pictures of 16:10. appShots exports `mac-2880`, which is 2880 × 1800. The store accepts three smaller sizes as well, and one size is all it needs. See [Sizes](/docs/sizes).

## Get the screens in

A Mac app runs on your Mac, not in a simulator. Open it, bring it to the screen you want to show, then capture its window:

```bash
appshots capture --mac Ledger --name overview --project ledger
```

`Ledger` is the app's name, as the menu bar shows it. The command takes the app's frontmost window, without the shadow macOS puts around it. To go through several screens, `shoot` names each one and waits while you open it:

```bash
appshots shoot ledger overview budgets reports --mac Ledger
```

**The first capture needs a permission.** macOS only lets a program take pictures of windows when the terminal it runs in is switched on under **System Settings → Privacy & Security → Screen & System Audio Recording**. The command says so when macOS refuses.

**Or take the window yourself.** Press Shift-Command-4, then Space, then click the window. macOS saves a PNG to the desktop. Upload it in the Studio, or with:

```bash
appshots upload ledger ~/Desktop/overview.png --as mac
```

`appshots run` and `appshots launch` are for the iOS Simulator. Build and start a Mac app the way the project does, such as with Xcode's Run button, `npm run tauri dev` or `npm run start`. A Tauri or Electron app that runs from source goes by the name of its program, not the name in the menu bar. See [Web apps and other toolkits](/docs/web-apps#a-desktop-app-from-tauri-electron-or-flutter).

## Which pictures count as Mac

| The image | It counts as |
| --- | --- |
| Is wider than it is tall | Mac |
| Has see-through round corners, as a captured window has | Mac, whatever its shape |
| Has the exact size of an iPhone or iPad screenshot turned on its side | That iPhone or iPad |

When one lands under the wrong device, change it with the small menu on its thumbnail under **Assets** in the Studio, or upload it again with `--as mac`.

## A window or a MacBook

A Mac screenshot is drawn in one of two ways, and the picture decides:

| The screenshot | Is drawn as |
| --- | --- |
| A captured window | That window, with its own round corners and a soft shadow |
| A picture that fills its rectangle and has about the shape of a display, such as an app in full screen | The screen of a MacBook |
| Any other shape, such as a tall panel | A window |

A display's shape here means between 1.45 and 1.85 times as wide as it is tall.

To choose yourself, set the slide's or the theme's `device`:

```json
{ "device": { "frame": "mac" } }
```

`"frame": "mac"` puts any screenshot in a MacBook, and `"frame": "none"` shows it as a bare window. In the Studio this is **Frame** in the slide's device settings. The MacBook is silver unless you pick another colour, takes the same looks as the other devices (`real`, `clay`, `outline`, `sketch`), and has no status bar.

**Which one to use.** A window suits most apps: it is drawn large, so its text stays readable. A full screen inside a MacBook turns into fine print, and works best as the first slide, where it says "this is a Mac app" at a glance.

## Slides on the wide canvas

Every [layout](/docs/slides#layouts) works for the Mac. The canvas is wide and short, so a few things differ from the portrait sets:

- `device-right` and `device-left` give the copy the narrower part and the device the wider one.
- There is little height. With a device below the copy, one line of headline and one of subheadline is the most that fits well.
- A MacBook that would be cut off only slightly at the bottom is made small enough to show whole.
- In `duo`, `side-by-side` and `trio`, laptops and windows stand level instead of fanned out.
- A `features` slide lays its cards across.

## Mac and iPhone together

A slide refers to a screenshot by name, and each device shows its own picture of that name. So for an app on both, capture the same screen under the same name on each device. The shared slide set then serves them all: the Mac sizes show the Mac's `overview`, the iPhone sizes the iPhone's.

That is the quick way, and the Mac's pictures come out as the iPhone's design on a wide canvas. A Mac set reads better when it is composed for that canvas: the copy beside the window, other screens, another order, fewer slides. Give the Mac a set of its own for that, in the same project:

- In the Studio, pick **Mac** above the slides, then **Give the Mac its own slides**.
- An agent calls `set_slides` with `"device": "mac"`.

The iPhone and the iPad keep the shared set, or get their own the same way. The theme and the languages stay shared, so the sets match. See [A set for each device](/docs/slides#a-set-for-each-device).

**One slide with both.** A name that only has an iPhone screenshot is drawn as an iPhone, also on the Mac canvas. Put a Mac screenshot and an iPhone one into a layout that takes several, and the laptop or window stands in the middle with the phone in front of a lower corner:

```json
{ "layout": "duo", "headline": "Also on your *iPhone*", "screenshot": "overview", "screenshot2": "phone" }
```

`trio` adds a third, such as an iPad on the other side.

## Mockups and banners

- [Mockups](/docs/mockups) draws a Mac screenshot as a MacBook or a window on a transparent background, for a website.
- [Banners](/docs/banners) makes the wide picture at the top of a README and the preview a link shows.

## With an agent

[The skill](/docs/skill) reads the project, sees that it is a Mac app and takes this route by itself. It builds and opens the app, captures its window on each screen, and renders the Mac set. For an app that is on the iPhone as well, it designs the Mac's slides as a set of their own. For the capture it needs the Screen Recording permission above. Without it, it asks you to take the windows yourself.

---

# Web apps and other toolkits

Source: https://shots.beffa.xyz/docs/web-apps

Your app does not have to be an Xcode project. An app is captured where it runs, and there are three such places:

| The app | Made with | It runs | A screen is captured with |
| --- | --- | --- | --- |
| iPhone or iPad app | Xcode, Flutter, React Native, Expo, Capacitor, Tauri | In the iOS Simulator, or on a plugged-in device | `appshots capture --clean` |
| Mac app | Xcode, Tauri, Electron, Flutter | In a window on your Mac | `appshots capture --mac <app>` |
| Web app | Next.js, Vite, SvelteKit, Rails, Django and the like | As pages in a browser | `appshots capture --web <address>` |

What differs from one toolkit to the next is how the app is started. Run this in the app's folder, and it reads the project and tells you:

```bash
appshots check
```

```text
Project: no Xcode workspace or project here, and 'appshots run' builds only those. This app is captured as its toolkit runs it:
Made with Next.js (package.json):
  In a browser: 'pnpm dev' in the background (usually at http://localhost:3000; it prints the address it took), then 'appshots capture --web http://localhost:3000 --name <name>'
Browser: Google Chrome, which draws the pages for 'appshots capture --web'
```

It knows Flutter, Expo, React Native, Capacitor, Tauri, Electron, the common web frameworks (Next.js, Nuxt, SvelteKit, Astro, Remix, Angular, Vite and others), Rails, Django, Laravel, Phoenix and a plain `index.html`. In a repository that holds several apps, it lists the folders they are in.

## A web app

Start the app the way you always do. Then every screen is an address:

```bash
appshots capture --web http://localhost:3000 --name home --project streaks
appshots capture --web http://localhost:3000/stats --name stats --project streaks
```

The command draws the page at the exact size of an iPhone screenshot, without opening a window, and uploads it. It uses the Chrome, Edge or Brave that is installed and downloads nothing. That browser runs with a profile of its own, so it sees none of your sign-ins, history or extensions.

It waits until the page has loaded and stopped fetching. If the address answers with an error such as 404, it says so. A file works as well: `--web index.html`.

### Sizes

| Option | The page is laid out at | The picture |
| --- | --- | --- |
| none | 440 × 956 points | 1320 × 2868, an iPhone's |
| `--as ipad` | 1032 × 1376 points | 2064 × 2752, an iPad's |
| `--as mac` | 1440 × 900 points | 2880 × 1800, a Mac's, shown inside a MacBook |
| `--as duo` | 669 × 951 points | 2007 × 2853, an iPhone Duo's |

`--viewport 1280x800` lays the page out at another size. For a set on several devices, capture each address once per device under the same name. Each device's slides then show its own picture. See [Sizes](/docs/sizes).

### The status bar and the home indicator

On an iPhone or iPad the page is laid out as an installed web app is: between the status bar and the home indicator. The home indicator is drawn. The status bar's place stays free, in the colour of the page's top edge, and the theme draws a clean one into it:

```json
{ "device": { "statusBar": "overlay" } }
```

In the Studio that is **Status bar** in the device settings. See [Status bar](/docs/slides#status-bar).

A page that asks for the whole screen with `viewport-fit=cover` gets it, together with the safe area a phone would report through `env(safe-area-inset-top)`. It then runs under the status bar, as it does on a phone.

### Reach a screen

| Option | What it does |
| --- | --- |
| `--init <script>` | JavaScript that runs before the page's own, on every load. For sample data in `localStorage`, a cookie banner that is already dismissed, a fixed date. `--init @seed.js` reads it from a file |
| `--eval <script>` | JavaScript that runs in the loaded page before the picture is taken: the click that opens a tab or a dialog, a scroll. `await` works in it. When it leads to another page, that page is captured |
| `--wait <seconds>` | Time the page gets to settle once it has loaded. One second unless you say otherwise |
| `--dark`, `--light` | The colour scheme the page is asked for |
| `--lang <code>` | The language the browser reports. The screenshot is filed under that [language](/docs/languages#screenshots-per-language) |
| `--profile <folder>` | Keeps cookies and storage in this folder between captures, for pages behind a sign-in |

```bash
appshots capture --web localhost:3000/stats --name stats --project streaks --dark \
  --init "localStorage.setItem('demo', '1')" \
  --eval "document.querySelector('[data-tab=week]').click()"
```

`appshots shoot` is for apps you open by hand, so it does not take `--web`. Screenshots you take in your own browser can be [uploaded](/docs/screens#from-files-you-have) like any others.

### What you need

Node.js 18 or newer and one of Google Chrome, Chromium, Microsoft Edge or Brave. `APPSHOTS_BROWSER` points the command at another Chromium browser. This kind of capture needs no Xcode.

## An iPhone or iPad app from another toolkit

It runs in the iOS Simulator like any other app. Only the build is the toolkit's own, so `appshots run`, which builds Xcode projects, is replaced by two commands:

```bash
appshots boot                       # boots the simulator the store sizes ask for and prints its udid
flutter run -d <udid>               # your toolkit's run command, with that udid
```

| Toolkit | Its run command |
| --- | --- |
| Flutter | `flutter run -d <udid>` |
| Expo | `npx expo run:ios --device <udid>`, or `npx expo start --ios` for Expo Go |
| React Native | `npx react-native run-ios --udid <udid>` |
| Capacitor | `npm run build && npx cap run ios --target <udid>` |
| Tauri | `npm run tauri ios dev "<simulator name>"` |

`boot` prints the one that fits the project in the current folder, with the udid filled in. `--ipad` and `--duo` boot those simulators instead. From there on everything is as in [Get your screens in](/docs/screens):

```bash
appshots capture --name home --project streaks --clean
appshots shoot streaks home stats detail --clean
```

Once the app is installed, `appshots launch <bundle id>` starts it again without a build.

A Capacitor app is its web pages inside a native shell. Capturing the pages with `--web` is quicker and looks the same, unless a screen depends on a native plugin such as the camera.

## A desktop app from Tauri, Electron or Flutter

It is a Mac app, and its window is captured as described in [Mac apps](/docs/mac):

```bash
npm run tauri dev                   # or npm run start, flutter run -d macos
appshots capture --mac pond --name overview --project pond
```

An app that runs from source goes by the name of its program instead of the name in the menu bar: `Electron` for an Electron app, the name of the Cargo package for a Tauri app. `appshots check` says which, and `appshots capture --mac` alone lists the apps that have a window open.

## With an agent

[The skill](/docs/skill) does all of this by itself. It runs `appshots check`, starts the app with the command that names, captures each screen, and stops what it started. For a web app it reads the routes off the router and reaches a screen by its address, with `--init` and `--eval` where a screen needs data or a click.

It uses the sample data and the test account the project already has, and never a real account. When a screen needs a sign-in it has no test account for, it asks you.

---

# The Studio

Source: https://shots.beffa.xyz/docs/studio

The Studio is the editor at [https://shots.beffa.xyz](/studio). It shows every slide as a live preview at the exact export size. Whatever an agent builds through MCP appears here within a few seconds, and everything it builds can be changed by hand.

Changes save by themselves. There is no Save button.

## Projects

One project per app. **All projects** lists them with their slides, their languages and when they last changed, and the sidebar keeps them one click away.

- **New project** asks for a name and the languages.
- **Rename:** click the project's title in the bar at the top and type, or use the three dots beside the project. The project's address stays the same, so links to it keep working.
- **Delete:** the three dots beside the project, or **Delete this project…** at the bottom of the **Project** tab. Its slides, screenshots, images and renders go with it. This cannot be undone.

A project has three parts, in the bar at the top: **Slides**, **Assets** and **Mockups**.

## Slides

A set has up to ten slides, in store order. The line above the previews sets what they show:

| Control | What it does |
| --- | --- |
| iPhone, iPhone Duo, iPad, Mac | The device the previews are for. The Mac's are landscape. See [Mac apps](/docs/mac) |
| The size beside it | Where a device has two shapes, such as the 13-inch and the 11-inch iPad, which one is shown |
| The language | The texts and screenshots shown, and the language you are typing for. **+ Language** adds one |
| S, M, L | How large the previews are |

- **+ Add slide** adds one at the end.
- Click a slide to edit it. Its fields open on the right, and the preview follows as you type.
- **Move earlier**, **Move later**, **Duplicate** and **Delete** are at the top of the fields.
- An image placed on the selected slide can be dragged in the preview.

### A set for each device

A new project has one set, and every device draws it on its own canvas. That gives a full export from one design.

A device can have a set of its own instead. **Give the iPad its own slides** under the device switch starts one as a copy of the shared set. From then on the two are changed separately: another layout, other screenshots, more or fewer slides. A dot on the device switch marks a device with its own set, and **Use the shared slides again** removes it.

The theme, the languages and the screenshots stay shared, so the sets match. See [Slides](/docs/slides#a-set-for-each-device).

### The fields of a slide

Each group folds away under its name.

| Group | What you set |
| --- | --- |
| Layout | One of 17, shown as small sketches. See [Slides](/docs/slides#layouts) |
| Screenshot | Click a thumbnail. Layouts with two or three devices take two or three |
| Text | Eyebrow, headline and subheadline. `*Asterisks*` mark a word, and Return forces a line break |
| Device | Size, position, tilt, 3D turn, frame colour and shadow. With two or three devices also the angle between them and their distance |
| Features | Cards on a feature slide with their look, icons and columns, the figures of a stat slide, the steps of a steps slide, or short points beside the headline |
| Badges | Wreath, stars, rating, review, icon or pill. On a reviews slide also the reviews of its wall |
| Icons and logos | Up to eight icons or logos with a label such as "Works with" |
| Your logo | An uploaded image over the headline |
| Images | Uploaded artwork placed anywhere on the slide |
| Style for this slide | Background, font, colours, alignment, headline size and how marked words look, for this slide only. Also the device look, from realistic to a hand-drawn sketch, and a clean status bar |
| JSON | The slide as the API sees it, to read or paste |

## The theme

The **Project** tab holds what the whole project shares: its name, its languages, the export sizes and the theme. The theme is the background, the font, the text and accent colour, the alignment and the device. Every slide of every set uses it unless the slide sets its own value. Change the theme once and everything follows.

## Assets

**Assets** holds the files the slides are made of.

**Screenshots** are grouped by device. Drop files anywhere in the window, or use **Upload**. Uploading a file with the name of an existing one replaces it everywhere. The tag on a screenshot is its device, and choosing another one files the screenshot there. A screenshot that a slide, a banner or a store image uses has its name in full colour. See [Get your screens in](/docs/screens).

**Logos and images** is everything else a slide can show: your logo or app icon, a mascot, stickers, shapes. Drop PNG, SVG, JPEG or WebP files on that section. A slide picks them under **Your logo** and **Images**. See [Logos and artwork](/docs/images).

## Header and search results

Under the slides. Adds, previews and edits the wide images for the Header and Search Results tab of App Store Connect. **Show safe area** draws the part every device shows as a dashed frame. The frame is a guide and never in the exported file. See [Header and search results](/docs/store-assets).

## Banners

Under them. Adds, previews and edits wide pictures for outside the store: a README banner, a repository's social preview, a link preview and a 16:9 picture for a post. They are shown whole wherever they go, so they have no safe area. See [Banners](/docs/banners).

## Languages

The language switch above the slides sets what the page shows: the texts, and each language's own screenshots. It is also the language you are typing and uploading for. **+ Language** next to it adds one, and **Project → Languages** lists them, removes one or makes another the first.

With a language other than the first picked, **Assets** shows that language's own screenshots. The dimmed ones are the first language's, standing in until you upload this language's version: drop the files under the same names, or use the arrow on a dimmed one. See [Languages](/docs/languages).

## Export ZIP

**Export ZIP** renders the whole project and downloads it: every slide of every set for every language and every display of App Store Connect, plus the header and search results images and the banners. **Project → Export sizes** lists the sizes and lets you add the older ones. See [Render and export](/docs/export).

The render runs on the server, in the background. You can keep editing, open another project or switch to another tab while it works. What you change after pressing the button is not in that render.

- **Renders** in the sidebar shows each render with its progress. The × beside a running one cancels it.
- When it is done, the ZIP downloads by itself. If you were on another tab, the tab's title says so and the download starts when you come back. **Download ZIP** in the list fetches it again.
- **Notify me when it is done** asks your browser for permission to show a notification, for when the tab is in the background.
- A render an agent starts shows up in the same list.
- The number beside **Renders** is how many your account has left. See [Accounts, tokens and limits](/docs/accounts#renders).

## Mockups

**Mockups** shows every screenshot in its device with nothing behind it, to download as a transparent PNG for a website or a README. The device's look, colour, shadow and tilt are set on the right and apply to all of them. See [Mockups](/docs/mockups).

## Settings

**Settings** in the sidebar, or a click on your name at the top of it.

| Part | What is there |
| --- | --- |
| Account | Who is signed in, **Sign out**, how much you keep on this server and how many renders you have left |
| Appearance | Auto, Light or Dark. Auto follows your device, and the browser remembers the choice |
| Agents and the command line | **Connect** for the setup command, and the command that brings an installed skill up to date. See [Update](/docs/update) |
| API tokens | Create and revoke the tokens your agents use. See [Accounts, tokens and limits](/docs/accounts) |
| Delete account | Removes your account with everything in it. The person who runs the server does not have it |

## Admin

The person who runs the server has **Admin** in the sidebar. It shows the server in numbers: accounts, projects, screenshots, renders made and storage used. Under them it sets who can sign in and lists the people, each with their projects, their storage and their renders. A click on a person's renders sets how many they have left.

The admin sees how much everybody keeps and never what it is. See [Accounts, tokens and limits](/docs/accounts).

## The sidebar

| Item | What it does |
| --- | --- |
| Your name | Opens the settings |
| All projects | Every project in a table |
| Projects | The projects, and **+** for a new one |
| Renders | The renders under way and the ones that just finished, and how many your account has left |
| Connect an agent | The setup command for your agent, with a token. See [Connect an agent](/docs/connect) |
| Docs | These pages |
| Admin | The server in numbers and who can use it. Only for the person who runs it |
| Settings | Your account, the appearance, tokens and updates |

---

# Logos and artwork

Source: https://shots.beffa.xyz/docs/images

Besides screenshots, a project holds images: your logo or app icon, a mascot, stickers, shapes. A slide can show one as its **logo** and place up to ten more **anywhere** on the canvas.

## Upload

In the Studio, open **Assets** and drop the files on **Logos and images**. From the Mac:

```bash
appshots upload streaks logo.svg mascot.png --image
appshots upload streaks AppIcon.png --image --name app-icon
```

`--image` is what says these are not screenshots. Like a screenshot, an image has a name, and uploading under the same name replaces it on every slide.

| | |
| --- | --- |
| Formats | PNG, SVG, JPEG, WebP. Transparency is kept |
| Size | Up to 20 MB a file |
| Stored as | A PNG of at most 2048 pixels a side. An SVG is drawn once, when it is uploaded |
| How many | 30 per project |

A PNG or SVG with a transparent background works best.

## A logo over the headline

Pick it under **Your logo** in the slide's editor, or set `logo` on the slide:

```json
{ "logo": { "image": "app-icon", "size": "medium", "shape": "app-icon" } }
```

- `size`: `small`, `medium` or `large`.
- `shape`:

| Shape | What it is |
| --- | --- |
| `plain` | The image as it is. For a wordmark or a logo with a transparent background. |
| `app-icon` | Cropped square with the rounded corners of an iOS icon and a soft shadow. For the app's own icon. |
| `circle` | Cropped round. |

The logo belongs on the first slide, not on every one.

## Artwork anywhere on a slide

In the Studio, select a slide and open **Images** in its fields. Click an image there to place it, then drag it in the slide's preview. Size, turn, opacity, shadow, mirroring and the layer are in the same group, with ready arrangements for several images at once.

As data, each entry of `images` is:

| Field | Meaning | Default |
| --- | --- | --- |
| `image` | The name of an uploaded image | Required |
| `x` | Where its centre is, in % of the canvas width from the left. -30 to 130 | 50 |
| `y` | Where its centre is, in % of the canvas height from the top. -30 to 130 | 50 |
| `size` | Its width in % of the canvas width. 2 to 200 | 30 |
| `rotate` | Degrees, -180 to 180 | 0 |
| `layer` | `front` lies over the device and the copy. `behind` lies on the background, under both | `front` |
| `opacity` | 0.05 to 1 | 1 |
| `shadow` | `none`, `soft` or `strong`. It follows the outline of the image | `none` |
| `flip` | Mirrored left to right | `false` |

Later entries lie over earlier ones.

```json
{
  "images": [
    { "image": "mascot", "x": 82, "y": 30, "size": 28, "rotate": 8, "shadow": "soft" },
    { "image": "sparkle", "x": 14, "y": 62, "size": 12, "layer": "behind", "opacity": 0.6 }
  ]
}
```

## Good to know

- One to three images per slide is plenty. Keep them clear of the headline and of the part of the screen the slide is about.
- Positions are percentages, so the same numbers land elsewhere on the wider iPad canvas. With an iPad set, look at both.
- **Use only your own artwork.** A logo is a trademark. For other companies' logos, see [What App Review accepts](/docs/app-review).
- Header and search results images take a logo too, but only your app's own. Apple does not allow other logos there.

---

# Languages

Source: https://shots.beffa.xyz/docs/languages

One project holds the set in every language. The layout and the look stay the same. The words change, and so can the screenshots inside the devices.

## Pick the languages

**New project** asks for them. Later you add one with **+ Language** beside the slides, or under **Project → Languages**. The menu lists the languages App Store Connect has. **Another code…** takes any other.

An agent does it with `create_project` or `update_project`:

```json
{ "project": "streaks", "locales": ["en-US", "de-DE", "pt-BR"] }
```

- A language is a code such as `en-US`, `de-DE` or `pt-BR`.
- **The first language is the one the others fall back to.** A language shows the first one's texts and screenshots until it has its own.
- A project holds up to 40 languages.

**Use the codes App Store Connect uses**, such as `en-US`, `de-DE`, `fr-FR`, `es-ES`, `pt-BR`, `ja` and `zh-Hans`. The exported folders then carry the names of the places the files go.

## Switch language

The language switch sits beside the slides, next to the device switch. It sets what the whole page shows: the slides, the header and search results images, and the screenshots. It also sets which language you are typing and uploading for.

## Translate

Pick a language, then type into the slide's fields. What you type belongs to that language.

As data, any text is either one string for every language, or one string per language:

```json
{
  "headline": { "en-US": "Build *habits* that stick", "de-DE": "*Gewohnheiten*, die bleiben" },
  "subheadline": "Streaks"
}
```

That goes for headlines, eyebrows, subheadlines, badge titles and subtitles, feature titles and texts, and the label over an icon row.

## Screenshots per language

Your app looks different in German than in English, so each language can have its own screenshots. They go under the same names, and a slide keeps naming its screenshot as before: `home` is the English one in the English set and the German one in the German set.

1. Upload the first language's screenshots as usual.
2. Start the app in the next language and take the same screens again.
3. Upload them for that language, under the same names.

**In the Studio.** Pick the language, open **Assets** and drop the files there. A dimmed screenshot is the first language's, shown because this language has none of its own there yet. The arrow on it uploads this language's version under that name, whatever the file is called.

**From the Mac.** `--lang` says which language a screenshot is for:

```bash
appshots launch com.example.streaks --lang de-DE --capture home --to streaks -- -appshots home
appshots upload streaks ./shots-de --lang de-DE
```

With `run` and `launch`, `--lang` also starts the app in that language, so one command does both. `--lang de` finds the project's `de-DE`.

**With curl.** Add `?locale=de-DE` to the upload. See [REST API](/docs/api#screenshots).

- The language has to be one of the project's. Add it first.
- One name holds one screenshot per device and language. An upload replaces the one that was there.
- A language without its own screenshot of a name shows the first language's. Nothing is left empty.
- Delete a language's screenshot and it shows the first language's again.

## What follows the language

| Follows the language | Stays the same |
| --- | --- |
| Every text on a slide | The layout and the look |
| Every text on a header or search results image | Logos and images you placed |
| The screenshots inside the devices, where the language has its own | |

## Change the first language

**Make first** under **Project → Languages** moves a language to the top. Nothing is lost: every language keeps its own screenshots and texts. Only the fallback changes, so a language that has none of its own now shows the new first one's.

## Remove a language

The × beside it under **Project → Languages**. Its folder leaves the export. Its texts and screenshots are kept, so adding the language again brings them back.

## Long words

Text shrinks to fit, so a long German headline comes out smaller than a short English one. Look at every language before you export. A forced line break (`\n`, or Return in the Studio) often reads better than the automatic one.

## Export

Every render and every export makes all languages, one folder each:

```
en-US/iphone-6.9/01.png
de-DE/iphone-6.9/01.png
```

To render some only, pass `locales` to `render`. See [Render and export](/docs/export).

---

# Render and export

Source: https://shots.beffa.xyz/docs/export

Rendering turns the slides into PNG files at the exact pixel sizes App Store Connect accepts. The previews in the Studio are drawn by the same code, so the files look like the previews.

## Three ways

**In the Studio.** Press **Export ZIP** on the project page. It renders everything in the background and downloads `<project>-screenshots.zip` when it is done. You can keep working meanwhile. See [In the background](#in-the-background).

**From the Mac.**

```bash
appshots pull streaks --render
appshots pull streaks --render --out ~/Desktop/streaks
```

The files land in `./store-screenshots/streaks`, or in the folder after `--out`. Without `--render` you get the files of the last render.

**Through an agent.** The `render` tool renders, shows the agent a contact sheet to check, and returns a link to every file. It can render single slides too:

```json
{ "project": "streaks", "slides": [3] }
```

Every call uses one of the account's renders, whatever it draws. See [How many renders](#how-many-renders).

## What is in the ZIP

```
README.txt
en-US/
  iphone-6.9/01.png … 08.png
  iphone-6.3/01.png … 08.png
  iphone-6.5/01.png … 08.png
  iphone-6.1/01.png … 08.png
  ipad-13/01.png … 08.png
  ipad-11/01.png … 08.png
  mac-2880/01.png … 08.png
  creative-assets/header.png
  creative-assets/search.png
  banners/readme.png
de-DE/
  …
```

One folder per language, then one per display of App Store Connect, with the slides numbered in store order. `README.txt` lists which folder goes onto which display. `banners` holds the pictures for outside the store, when the project has any. See [Banners](/docs/banners). The files are PNGs without transparency at the exact pixel size of their display, which is what App Store Connect asks for.

## Which sizes

You design the set once per device. The export holds it at every size:

| Your project has | The export holds |
| --- | --- |
| iPhone screenshots | `iphone-6.9`, `iphone-6.3`, `iphone-6.5`, `iphone-6.1` |
| iPhone Duo screenshots as well | `iphone-duo` too |
| iPad screenshots as well | `ipad-13` and `ipad-11` too |
| Mac screenshots as well | `mac-2880` too |

Sizes of the same shape are the same picture scaled, so they look alike on every display. The sizes for older devices are left out until you tick them under **Project → Export sizes**. See [Sizes](/docs/sizes).

## How long it takes

Every language and every device is a full pass over the set. A set in three languages takes about three times as long as the same set in one. The extra sizes of a device cost next to nothing, because they are the same picture scaled.

The server renders one job at a time. Yours waits its turn if somebody else's is running. One account can have one render running and one waiting. A third is refused with "Two renders are already under way for this account", so wait for one to finish.

## In the background

A render started in the Studio runs on the server and does not need the page. Keep editing, open another project, switch to another tab or close the laptop lid: the render goes on.

- **It draws the project as it was when you pressed the button.** What you change while it runs is in the next render, not in this one.
- **Renders** in the sidebar shows the progress, and the × beside a running render cancels it.
- When it is done, the ZIP downloads by itself. If the tab was in the background, its title says the render is done and the download starts when you look at it again.
- **Notify me when it is done** lets your browser show a notification. It asks for permission once.
- A finished render stays in the list for ten minutes, and **Download ZIP** fetches the files again. They are kept until the next render replaces them.

A render stops if the server restarts, for instance for an update. The Studio says so, and you start it again. It has not used a render.

## How many renders

An account gets 10 renders. One render is one press of **Export ZIP**, one `appshots pull --render`, or one call of the `render` tool, whatever it draws: a single slide counts the same as the whole set in every language.

- Previews in the Studio are free. You only use a render when you export.
- A render that fails or that you cancel uses none.
- The Studio shows how many are left beside **Renders** in the sidebar, and the `render` tool tells the agent after each call.
- Downloading the ZIP of the last render again uses none.

When none are left, ask the person who runs the server for more. Their own renders are not counted.

## Upload to App Store Connect

1. Open your app in [App Store Connect](https://appstoreconnect.apple.com), then the version you are preparing.
2. Under **Previews and Screenshots**, pick the language at the top right.
3. Open a display and drag all files of its folder onto it. The file names keep them in order.
4. Do the same for each language.
5. Header and search results images go on the **Header and Search Results** tab. See [Header and search results](/docs/store-assets).
6. Banners are not for App Store Connect. See [where each one goes](/docs/banners#where-each-one-goes).

| Display in App Store Connect | Folder |
| --- | --- |
| iPhone with Dynamic Island (large display) | `iphone-6.9` |
| iPhone with Dynamic Island (medium display) | `iphone-6.3` |
| iPhone with Face ID (large display) | `iphone-6.5` |
| iPhone with Face ID (medium display) | `iphone-6.1` |
| iPhone Duo | `iphone-duo` |
| iPad 13" display | `ipad-13` |
| iPad 11" display | `ipad-11` |
| Mac, on the macOS version of the app | `mac-2880` |

**A display only takes its own sizes.** A file from another folder is refused as having the wrong dimensions. A display you leave empty is filled by App Store Connect from a larger one, so you can also upload the largest iPhone and iPad folders alone.

App Store Connect takes up to ten screenshots per language and display. An appShots set has at most ten slides, so a whole set fits.

## Render again

A render replaces the last one. Render again after you change a slide, replace a screenshot, or when [an update](/docs/update) redraws a layout. Projects are never rendered by themselves.

---

# Mockups

Source: https://shots.beffa.xyz/docs/mockups

A mockup is one of your screenshots inside its device, with nothing behind it. The file is a PNG with a transparent background, so it sits on a page of any colour: a landing page, a GitHub README, a press kit, a slide deck.

Mockups come from the same raw screenshots as the App Store set. A project needs no slides, no headlines and no theme to have them. They are not for App Store Connect, which accepts no transparency. For that, see [Render and export](/docs/export).

## Three ways

**In the Studio.** Open the project and pick **Mockups** in the bar at the top. Every screenshot is shown in its device. Set the device on the right, then press **PNG** under a mockup, **Copy** to paste it into a design or a document, or **Download all** for a ZIP.

**From the Mac.**

```bash
appshots mockups streaks
appshots mockups streaks --style clay --shadow soft --width 1200
```

The files land in `./mockups/streaks`, or in the folder after `--out`. See [the command's options](/docs/cli#mockups).

**Through an agent.** The `render_mockups` tool draws them and returns a link to every PNG and a picture to check. Ask for it in plain words, such as "give me transparent iPhone mockups of these screens for the website". See [MCP tools](/docs/mcp#render-mockups).

## What you get

One PNG per screenshot and device, named after both:

```
home-iphone.png
stats-iphone.png
home-ipad.png
home-mac.png
```

Each file is cut to the device and its shadow, with no margin around it. **Download all** and `appshots mockups` deliver them as `<project>-mockups.zip`.

Left alone, a mockup shows the screenshot pixel for pixel, which is the sharpest it gets. A 1320 pixel wide iPhone screenshot makes a PNG about 1430 pixels wide, because the frame adds to it. Set a width when you need a certain size. A tilted device makes a wider picture than an upright one.

## The device

The same settings apply to every mockup of a project. They are saved with the project and are separate from the theme of the App Store set, so changing one never changes the other.

| Setting | What it does |
| --- | --- |
| Device look | `real`, the default, or `clay`, `outline` or `sketch` |
| Device colour | `black`, `silver`, `natural`, `gold`, `blue` or `orange` |
| Frame | Matches the screenshot unless you pick one. `none` gives the bare screenshot with rounded corners. A Mac screenshot comes out as a window or inside a MacBook, as on a slide. See [Mac apps](/docs/mac#a-window-or-a-macbook) |
| Shadow | `none`, the default, or `soft` or `strong`. The shadow is part of the PNG and falls straight down |
| Tilt | Turns the device, from -45 to 45 degrees |
| Turn in 3D | Turns it towards the left or the right |
| Status bar | `cover` or `overlay` draws a clean one, with the time you set. See [Slides](/docs/slides#status-bar) |
| Width | The width of each PNG in pixels, from 200 to 4000. Left empty: pixel for pixel |

A line drawing (`outline`, `sketch`) is near black unless you pick a device colour. On a dark page, pick `silver`.

The **Transparent**, **Light** and **Dark** switch above the mockups only changes what the previews stand on, to see how they look on your page. The PNG stays transparent.

## Languages

A project in several languages has a mockup per language wherever that language has its own screenshot. The language switch above the mockups picks which ones you see. **Download all** holds the first language's mockups, and a folder per other language with the ones that differ. `appshots mockups streaks --lang de-DE` fetches one language only. See [Languages](/docs/languages#screenshots-per-language).

## On a web page

A mockup at its own size is larger than most pages need, which keeps it sharp on high-resolution screens. Let the page scale it:

```html
<img src="home-iphone.png" alt="The home screen of Streaks" width="320">
```

In a README:

```markdown
<img src="docs/home-iphone.png" alt="The home screen of Streaks" width="320">
```

## What it costs

Nothing. Drawing mockups does not use any of the account's [renders](/docs/export#how-many-renders). A mockup is drawn when it is asked for, which takes a second or two each, so a ZIP of many takes a moment.

For a wide picture with a headline and a background, such as the top of a README, see [Banners](/docs/banners).

---

# Banners

Source: https://shots.beffa.xyz/docs/banners

A banner is a wide picture for outside the store: your app's screens in their devices, a headline, your background. appShots makes four of them from the screenshots, theme and fonts the project has already.

They are optional. A project has none until you add one.

## The four banners

| Banner | Pixels | Used for |
| --- | --- | --- |
| `readme` | 2400 × 800 (3:1) | The wide picture at the top of a README or a web page. |
| `social` | 1280 × 640 (2:1) | The picture a link to a GitHub repository shows, at the size GitHub asks for. |
| `link` | 1200 × 630 (1.91:1) | The picture a link to a web page shows in chats and feeds: its og:image. |
| `post` | 1920 × 1080 (16:9) | For a post, a slide of a deck or a video thumbnail. |

A banner is shown whole wherever it goes. It has no safe area, unlike the [header and search results](/docs/store-assets) images, which the store crops.

## Make them

**In the Studio:** the **Banners** section of the project page adds, previews and edits each one. A new banner starts as a copy of one you have already, or else with the headline and screenshots of your slides.

**Through an agent:** ask for it in plain words, such as "make a banner for the README". The agent uses `set_creative_assets`, then `render`:

```json
{
  "project": "ledger",
  "readme": {
    "headline": "Your money, *at a glance*",
    "subheadline": "A calm budget app for the Mac.",
    "screenshots": ["overview", "phone"]
  }
}
```

```json
{ "project": "ledger", "creatives": ["readme"] }
```

With `creatives` alone, the render draws only those and leaves the slides as they are.

Each banner you pass replaces that one, and the others stay. `"remove": ["post"]` deletes one.

## Layouts

| Layout | What it is |
| --- | --- |
| `fan` | Copy at the top of the safe area, up to five devices fanned out below it and bleeding off the bottom edge. The first screenshot takes the centre. |
| `sides` | Copy in the middle of the safe area, up to four devices standing left and right of it. With one screenshot the device stands on the right. |
| `wall` | Copy over a dimmed, tilted wall of bare screenshots that fills the whole canvas. |
| `split` | Copy in a column on the left, up to three devices on the right. Made for banners. On an App Store asset the copy would leave the safe area, so it is drawn as sides there. |

A banner uses `split` unless you pick another: the copy on the left, up to three devices on the right. `fan` takes up to five screenshots, `sides` up to four and `wall` up to six, with the copy in the middle.

## Fields

The same as for the header and search results images:

| Field | What it is |
| --- | --- |
| `layout` | `split`, `fan`, `sides` or `wall`. Default `split` |
| `eyebrow`, `headline`, `subheadline` | The copy, as on a slide. Keep it short |
| `screenshots` | Names of uploaded screenshots, most important first. Without any, the banner is copy on the background |
| `badges` | Up to three, as on a slide |
| `logo` | Your app's own logo or icon. See [Logos and artwork](/docs/images) |
| `background`, `textColor`, `accentColor`, `font`, `textScale` | As on a slide. Left out, the project's theme decides |
| `device` | `frame`, `style`, `color`, `scale` and `shadow` |

## Mac, iPad and iPhone in one banner

Every screenshot is drawn as the device it is from: an iPhone one as an iPhone, a Mac one as a MacBook or a window. One banner can mix them. Name the Mac screenshot first and it stands in the middle, with a phone or a tablet in front of its lower corners.

## Where each one goes

A render writes the banners into a folder of their own, one per language:

```
en-US/banners/readme.png
en-US/banners/social.png
en-US/banners/link.png
en-US/banners/post.png
```

`appshots pull ledger` saves them under `./store-screenshots/ledger/en-US/banners/`. See [Render and export](/docs/export).

**`readme`.** Copy the file into the repository, such as `docs/banner.png`, and make it the first line of the README:

```markdown
![Ledger](docs/banner.png)
```

**`social`.** On GitHub, open the repository's **Settings → General → Social preview** and upload the file. GitHub takes a file of up to 1 MB there. The render warns when the file is larger. A plainer background without grain makes it smaller.

**`link`.** Put the file on your website and name it in the page's head:

```html
<meta property="og:image" content="https://example.com/link.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
```

**`post`.** A 16:9 picture for a post, a slide of a deck or a video thumbnail.

## Rules

Apple does not review banners, so its rules for store images do not apply. Figures, awards and reviews you show still have to be real.

For the devices alone, with nothing behind them, see [Mockups](/docs/mockups).

---

# What App Review accepts

Source: https://shots.beffa.xyz/docs/app-review

Screenshots are part of what Apple reviews. A set that breaks the rules gets the whole submission sent back. This page is a short summary in our own words. Apple's own pages decide, and they change:

- [App Review Guidelines](https://developer.apple.com/app-store/review/guidelines/), section 2.3 on accurate metadata
- [Screenshot specifications](https://developer.apple.com/help/app-store-connect/reference/screenshot-specifications)
- [Marketing resources and identity guidelines](https://developer.apple.com/app-store/marketing/guidelines/)

## Show the real app

- **Use real screens of the current version.** No mock-ups of features the app does not have, and no screens of an older design.
- **Show the app in use.** A set of title art, splash screens and sign-in pages gets rejected. Most slides should show a screen that does something.
- **Text around the device is fine.** Headlines, backgrounds and device frames are normal. They must not hide or misstate what the app does.
- **Show what everybody can reach.** If a slide shows something that needs a purchase or a subscription, say so on the slide.

## Only real numbers

A badge with a rating, a download count, an award or a review is a claim. So is a figure on a `stat` slide and a quote on a `reviews` slide.

- Use the figures your app really has, and keep them current.
- A review quote must be a real review. Name who said it only with their permission.
- "#1", "best" and "most popular" need proof you can show Apple.
- Do not show a price. Prices differ by country and change.

appShots has no way to check these, so it is up to you. The [skill](/docs/skill) never invents them.

## Other companies' names and logos

- **Do not name other platforms.** No "also on Android", and no other store's badge.
- **A logo is a trademark.** Show the logo of a service only if your app really works with it, and check that service's brand rules. Many allow "works with" and forbid anything that reads as a partnership or an endorsement.
- An icon row labelled "Works with" is the honest form. A logo wall with no label reads as an endorsement.
- The brand icons in appShots are there for this one use. That they are included does not give you the right to use them.

## Apple's own things

- Device frames must show the right kind of device: iPhone screens in an iPhone, iPad screens in an iPad. appShots picks the frame from the device the screenshot is from.
- Do not put the Apple logo, the App Store badge or other Apple artwork on a slide.
- Do not write Apple's product names into a headline as if Apple endorsed the app.

## Header and search results images

These have stricter rules of their own:

- Copy and the main subject stay inside the safe area. The rest is cropped on some devices.
- No other company's logo. Your own logo or app icon is fine.
- Keep the copy to one short phrase.

See [Header and search results](/docs/store-assets).

## Content

- Screenshots must suit everybody, whatever the app's age rating. No violence or adult content in the store images.
- Do not show real people's names, messages, addresses or faces without permission. Use made-up content.
- Keep the simulator's debug banners, test data and placeholder text out.

## A checklist before you submit

1. Does every device show a real screen of this version?
2. Is every figure, award and quote real and current?
3. Is there no price, no other platform and no Apple logo?
4. Does your app really work with every logo you show?
5. Does every language read well, with no cut-off words?
6. Are the files the right size? See [Sizes](/docs/sizes).

appShots is not affiliated with Apple and cannot promise that a set passes review.

---

# Slides

Source: https://shots.beffa.xyz/docs/slides

A set is a list of up to ten slides. A slide is one screenshot image for the store: a layout, some copy, and a screenshot inside a device. This page lists every field. The Studio's editor sets the same fields, so it reads as a map of the editor too.

A project has one set that every device shares, and any device can have a set of its own. See [A set for each device](#a-set-for-each-device).

```json
{
  "layout": "device-bottom",
  "eyebrow": "Streaks",
  "headline": "Build *habits* that stick",
  "subheadline": "One tap a day",
  "screenshot": "home",
  "badges": [{ "type": "rating", "title": "4.8", "subtitle": "App Store rating", "stars": 4.8 }]
}
```

Every field is optional.

## Layouts

`layout` decides where the copy and the devices go. The default is `device-bottom`.

| Layout | Screenshots | What it is |
| --- | --- | --- |
| `device-bottom` | 1 | Copy on top, device below and bleeding off the bottom edge. The classic store slide. |
| `device-top` | 1 | Device bleeding off the top edge, copy underneath. |
| `device-center` | 1 | Copy on top, the whole device visible below it. |
| `tilted` | 1 | Like device-bottom, with the device rotated and pushed off-centre for energy. |
| `closeup` | 1 | Like device-bottom, with the device larger than the canvas, so the top of the screen reads as a detail shot. |
| `floating` | 1 | Copy on top, the whole device below it, turned in perspective. |
| `duo` | 2 | Two overlapping devices (screenshot in front, screenshot2 behind) under the copy. |
| `side-by-side` | 2 | Two upright devices next to each other (screenshot left, screenshot2 right), for two steps or a before and after. |
| `trio` | 3 | Three devices fanned out: screenshot in front, screenshot2 and screenshot3 behind it left and right. |
| `device-right` | 1 | Copy in a column on the left, the device standing on the right and bleeding off that edge. Made for more text: a headline with a list of feature points under it. |
| `device-left` | 1 | The mirror of device-right: the device on the left, the copy column on the right. |
| `hero` | 1 | Social-proof opener: big badges above the headline, device peeking in from the bottom. Without a screenshot it becomes a centred text slide. |
| `features` | 0 | No device: the copy on top and the slide's feature cards below it, each an icon, a big figure or a small picture with a title. The card style picks the look. |
| `statement` | 0 | No device: the headline set very large, as the picture itself. For a claim, a promise or a question. |
| `stat` | 0 | No device: the slide's first feature as one giant figure with what it counts under it, and the next ones as a row of smaller figures. |
| `steps` | 0 | No device: the slide's features as numbered steps joined by a line, each a title and one line. A step can show a small picture of its screen. |
| `reviews` | 0 | No device: the slide's review badges as a wall of quote cards under the copy, each with its stars, the quote and who wrote it. |

Layouts with two or three devices read `screenshot2` and `screenshot3` as well.

The last five have no device. See [Slides without a device](#slides-without-a-device).

## Copy

| Field | What it is |
| --- | --- |
| `eyebrow` | A small uppercase line above the headline |
| `headline` | The main line. Two to five words read best |
| `subheadline` | One supporting line under it |

- `*Asterisks*` around a word mark it. By default it takes the accent colour, and `emphasis` picks another way. See [Marked words](#marked-words).
- `\n` forces a line break.
- Text shrinks to fit its space, so nothing is cut off.
- Each text is a string, or one string per language. See [Languages](/docs/languages).

## Screenshots

`screenshot`, `screenshot2` and `screenshot3` hold the names of uploaded screenshots. A name that does not exist leaves the device empty, and the render warns about it. See [Get your screens in](/docs/screens).

## The look

These fields can be set once in the project's **theme** and again on any slide. The slide wins.

| Field | Values | Default |
| --- | --- | --- |
| `background` | A preset, `auto`, a hex colour, or a custom object. See below | |
| `textColor` | A hex colour | Black or white, whichever reads on the background |
| `accentColor` | A hex colour, for tinted words, the eyebrow and badge stars | |
| `font` | A font id. See below | |
| `align` | `center` or `left` | `center` |
| `textScale` | 0.5 to 1.5. A multiplier for the headline size | 1 |
| `emphasis` | `tint`, `marker`, `underline`. How a headline shows its `*marked*` words | `tint` |
| `device` | The device options. See below | |

### Marked words

| Value | What it is |
| --- | --- |
| `tint` | The words take the accent colour. Without one, the rest of the headline is dimmed instead. |
| `marker` | A band of the accent colour behind the words, like a highlighter. |
| `underline` | A thick line of the accent colour under the words. |

## Backgrounds

- **A preset:** `midnight`, `aurora`, `ember`, `ocean`, `sunset`, `candy`, `forest`, `graphite`, `peach`, `mint`, `lavender`, `sky`, `paper`, `ink`
- **`auto`:** colours taken from the slide's own screenshot.
- **A hex colour:** `"#0F172A"`.
- **A custom one:**

```json
{ "type": "mesh", "colors": ["#1E3A8A", "#7C3AED", "#EC4899"], "angle": 165, "noise": 0.2 }
```

`type` is `solid`, `gradient` or `mesh`. `colors` takes one to five hex colours. `angle` is the direction of a gradient in degrees, and `noise` is film grain from 0 to 1.

## Device

| Field | Values | Default |
| --- | --- | --- |
| `frame` | `auto`, `iphone`, `duo`, `ipad`, `mac`, `none`. `auto` matches the screenshot, `duo` is the unfolded iPhone Duo, `mac` is a MacBook, and `none` shows the bare rounded screen, which for a Mac screenshot is the window alone. See [Mac apps](/docs/mac#a-window-or-a-macbook) | `auto` |
| `style` | `real`, `clay`, `outline`, `sketch`. How the frame is drawn. See [Device look](#device-look) | `real` |
| `color` | `black`, `silver`, `natural`, `gold`, `blue`, `orange` | `black` |
| `scale` | 0.4 to 1.6 | 1 |
| `rotate` | -45 to 45 degrees. On a layout with several devices it turns them all | 0 |
| `spread` | 0 to 60 degrees. The angle between the devices of `duo`, `side-by-side` and `trio` | 11 for `duo`, 14 for `trio`, 0 for `side-by-side` |
| `gap` | -30 to 30, in % of the canvas width. Pushes those devices apart, or together when negative | 0 |
| `perspective` | `none`, `left`, `right`. A 3D turn towards one side | `none` |
| `offsetX` | -60 to 60, in % of the canvas width | 0 |
| `offsetY` | -60 to 60, in % of the canvas height | 0 |
| `shadow` | `none`, `soft`, `strong` | `soft`, and `none` for an outline or a sketch |
| `statusBar` | `none`, `cover`, `overlay`. See [Status bar](#status-bar) | `none` |
| `statusBarTime` | The time the status bar shows, such as `9:41` | `9:41` |
| `statusBarColor` | `auto`, `light` (white), `dark` (black) | `auto` |

### Device look

`style` is how the frame is drawn. Set it once in the theme, so the whole set matches.

| Look | What it is |
| --- | --- |
| `real` | The device as it looks: a metal band, a black bezel and its buttons. The default. |
| `clay` | One matte colour all over, like a clay model. Calm, and it leaves the attention on the screen. |
| `outline` | A thin line drawing of the device around the screenshot. The line takes the text colour, or the device colour when one is set. |
| `sketch` | The line drawing with the wobble of a pen, as if drawn by hand. |

### Two or three devices

On `duo`, `side-by-side` and `trio`, `spread` is the angle between the devices. At 0 they stand parallel, and a larger angle fans them out. `gap` moves them apart or together, and `rotate` turns the whole group.

A group that mixes a Mac screenshot with iPhone or iPad ones is arranged differently: the laptop or window stands in the middle, and the phones and tablets stand in front of its lower corners. `spread` does not apply there. See [Mac apps](/docs/mac#mac-and-iphone-together).

```json
{ "layout": "duo", "screenshot": "home", "screenshot2": "stats", "device": { "spread": 24, "gap": 4 } }
```

### Status bar

A screenshot from a real device shows whatever the device showed: an odd time, a low battery, one bar of signal. `statusBar` draws a clean one instead, with the time, full signal, Wi-Fi and a full battery.

| Value | What it is |
| --- | --- |
| `none` | The screenshot as it is. The default. |
| `cover` | Painted over the status bar the screenshot came with, in the colour of the screenshot's top edge. For a screenshot from a real device, with its odd time and half-empty battery. |
| `overlay` | Drawn straight onto the screenshot, nothing covered. For a screenshot that has no status bar. |

`statusBarColor` is read off the screenshot unless you set it. Screenshots taken with `appshots capture --clean` in the simulator are clean already and need none of this. A web app's pages taken with `appshots capture --web` have no status bar and leave its place free: `overlay` draws one there.

## Badges

A slide draws up to 4 with its copy, from `badges`. `badgePosition` puts them at the `top`, above the headline, or at the `bottom`, below the subheadline. The list itself holds up to ten, because the `reviews` layout also reads its review badges. See [Reviews](#reviews).

| Type | What it is |
| --- | --- |
| `laurel` | Laurel wreath around a title and subtitle. Add `stars` or `icon` for a top line. The award look. |
| `stars` | A row of stars over a title and subtitle, no wreath. |
| `icon` | An icon (trophy by default) next to a title and subtitle. |
| `pill` | A small capsule label with an optional icon. |
| `rating` | A card with the score large on the left, the star row and a subtitle beside it. The App Store rating look. |
| `review` | A card with a star row, a short quote as the title and who said it as the subtitle. |

| Field | What it is |
| --- | --- |
| `title` | The big line, such as "10K+" or "4.9". On a review it is the quote |
| `subtitle` | The small line, such as "Downloads". On a review it is who said it |
| `stars` | 0 to 5. Shows a row of stars filled to this value |
| `icon` | An icon id |
| `tone` | See the table below |

| Tone | What it is |
| --- | --- |
| `plain` | The wreath in the text colour, stars and icon in the accent colour (gold stars without one). |
| `gold` | Wreath, stars and icon in a metallic gold. The award look. |

Badges are claims. Use real figures only. See [What App Review accepts](/docs/app-review#only-real-numbers).

## Features

`features` holds up to eight entries, most important first. On the `features` layout they are the cards that fill the slide, on `stat` the figures, and on `steps` the steps. On every other layout they are short points beside the headline.

| Field | What it is |
| --- | --- |
| `title` | The feature, in two to four words. Required |
| `text` | One short supporting line |
| `icon` | An icon id |
| `value` | A big figure or word in place of the icon, such as "0", "24h" or "100%". On `stat` it is the figure, and on `steps` it replaces the step's number |
| `color` | A hex colour for this entry alone: its icon, its figure and its card. Left out, the accent colour |
| `image` | The name of an uploaded image shown in place of the icon, such as a small illustration. See [Logos and artwork](/docs/images) |
| `screenshot` | The name of an uploaded screenshot shown small. On a card it stands under the title and runs off the card's lower edge, and on a step it stands beside the text |

`featureStyle` on the `features` layout:

| Style | What it is |
| --- | --- |
| `bento` | Tiles of mixed sizes, like the summary slide of a keynote. The first feature gets the large tile. |
| `grid` | Even tiles side by side, icon above the title. |
| `cards` | One wide card per feature, icon on the left, title and detail beside it. |
| `list` | Plain rows without cards, like a What's New screen. |

`featureStyle` for points on the other layouts:

| Style | What it is |
| --- | --- |
| `list` | Rows of an icon, a title and one line of detail. |
| `cards` | The same rows, each on a card. |
| `grid` | Cards two across, where the copy is wide enough for them. |
| `chips` | Small pills with an icon and the title only, in rows. |

### The cards

`featureCards` sets what the cards of the `features` layout are made of.

```json
{ "layout": "features", "featureStyle": "grid", "featureCards": { "look": "solid", "icons": "badge", "columns": 2 } }
```

| Field | Values | Default |
| --- | --- | --- |
| `look` | See below | `glass` |
| `icons` | See below | `gradient` |
| `columns` | 1 to 4 cards across, for `grid`, `cards` and `list`. A bento arranges itself | The canvas decides |

| Look | What it is |
| --- | --- |
| `glass` | A translucent film over the background. The first card of a bento, and any card with a colour of its own, is washed in that colour. |
| `tint` | Every card washed in its colour: its own, or the accent colour. |
| `solid` | Every card filled with its colour, the text on it black or white. Blocks of colour. |
| `outline` | No fill, a thin line around each card. |

| Icons | What it is |
| --- | --- |
| `gradient` | The icon alone, shaded from the text colour to the card's colour. |
| `badge` | The icon on a rounded square of the card's colour, like an app icon. |
| `plain` | The icon alone, in the text colour. |

## Slides without a device

Five layouts show no device. They carry what a screenshot cannot: a claim, a number, a way of working, what people say. One or two in a set of ten is plenty, and the first slides should still show the app.

| Layout | What it reads | Good for |
| --- | --- | --- |
| `features` | `features`, as cards | What the app does, at a glance |
| `statement` | The headline alone, set very large | A claim, a promise or a question |
| `stat` | `features`: the first is the giant figure, the next ones a row of smaller figures | One number that carries the pitch |
| `steps` | `features`, as numbered steps | How it works, in three or four steps |
| `reviews` | The `review` badges, as a wall of quote cards | What users and the press say |

```json
{
  "layout": "stat",
  "headline": "Loved by *runners*",
  "features": [
    { "value": "2M+", "title": "Runs logged" },
    { "value": "4.8", "title": "App Store rating" },
    { "value": "120", "title": "Countries" }
  ]
}
```

```json
{
  "layout": "steps",
  "headline": "Up and running in *a minute*",
  "features": [
    { "title": "Pick a goal", "text": "Distance, time or pace.", "screenshot": "goal" },
    { "title": "Run", "text": "The watch does the counting." },
    { "title": "See the week", "text": "Every run on one page." }
  ]
}
```

### Reviews

The `reviews` layout draws up to 6 badges of the type `review` as its wall. `title` is the quote, `subtitle` is who said it, and `stars` adds the row of stars. A publication works the same way, with its name as the `subtitle`. Badges of other types stay with the copy as on any slide.

```json
{
  "layout": "reviews",
  "headline": "People *stay*",
  "badges": [
    { "type": "review", "title": "The only tracker I kept past January.", "subtitle": "Maya, App Store review", "stars": 5 },
    { "type": "review", "title": "Quietly excellent.", "subtitle": "The Sweet Setup" }
  ]
}
```

Figures and quotes are claims. Use real ones only, and a quote only with the right to use it. See [What App Review accepts](/docs/app-review#only-real-numbers).

## Icon row

A row of up to eight icons or brand logos with the copy, for the services your app works with.

```json
{ "iconRow": { "label": "Works with", "icons": ["spotify", "slack", "notion"], "style": "tiles" } }
```

| Field | Values | Default |
| --- | --- | --- |
| `icons` | One to eight icon ids, in the order shown | Required |
| `label` | A short line over the row | |
| `style` | See below | `tiles` |
| `tone` | See below | `brand` |
| `size` | `small`, `medium`, `large` | `medium` |
| `position` | `top` or `bottom` | `bottom` |

| Style | What it is |
| --- | --- |
| `tiles` | Each icon on a rounded square, like a row of app icons. |
| `circles` | Each icon on a round disc. |
| `plain` | The icons alone, side by side. The logo strip look. |

| Tone | What it is |
| --- | --- |
| `brand` | Brand icons in the service's own colours, such as YouTube's red; every other icon in the accent colour. |
| `accent` | Every icon in the accent colour. |
| `text` | Every icon in the text colour, for a quiet strip of logos. |

## Logo and images

`logo` shows an uploaded image over the headline, and `images` places up to ten anywhere on the slide. See [Logos and artwork](/docs/images).

## A set for each device

`slides` is the set every device shares. It is drawn again for each canvas, so one design gives an iPhone, an iPad and a Mac set. That is the quickest way to a full export, and the results look alike.

A device can have its own set instead, in `deviceSlides`. The iPad's or the Mac's canvas has another shape and another reader, so a set composed for it usually reads better: another layout, more text beside the device, other screenshots, a different number of slides.

```json
{
  "slides": [{ "headline": "Build *habits* that stick", "screenshot": "home" }],
  "deviceSlides": {
    "ipad": [{ "layout": "device-right", "headline": "Room for the *whole week*", "screenshot": "week" }],
    "mac": [{ "layout": "device-left", "headline": "Plan at *your desk*", "screenshot": "overview" }]
  }
}
```

- The devices are `iphone`, `duo`, `ipad` and `mac`. Each takes one to ten slides.
- A device with its own set draws that set and nothing from `slides`. The other devices keep the shared one.
- The theme, the languages and the screenshots belong to the project, so the sets match in colour and font without repeating them.
- In the Studio, **Give the iPad its own slides** under the device switch starts a set as a copy of the shared one. **Use the shared slides again** removes it.
- An agent passes `device` to `set_slides`, and `of` to `update_slide`. See [MCP tools](/docs/mcp).

## Fonts

41 fonts, all served by the server itself. The value of `font` is the id in brackets.

- **Sans:** Inter (`inter`), Manrope (`manrope`), Plus Jakarta Sans (`plus-jakarta-sans`), DM Sans (`dm-sans`), Outfit (`outfit`), Figtree (`figtree`), Geist (`geist`), Onest (`onest`), Urbanist (`urbanist`), Lexend (`lexend`), Montserrat (`montserrat`), Rubik (`rubik`), Red Hat Display (`red-hat-display`), Archivo (`archivo`), Sora (`sora`), Space Grotesk (`space-grotesk`)
- **Rounded:** Nunito (`nunito`), Quicksand (`quicksand`), Fredoka (`fredoka`), Baloo 2 (`baloo-2`)
- **Display:** Bricolage Grotesque (`bricolage-grotesque`), Unbounded (`unbounded`), Syne (`syne`), Oswald (`oswald`), Anton (`anton`), Bebas Neue (`bebas-neue`), Archivo Black (`archivo-black`)
- **Serif:** Fraunces (`fraunces`), Playfair Display (`playfair-display`), Lora (`lora`), Newsreader (`newsreader`), Cormorant (`cormorant`), Roboto Slab (`roboto-slab`), Instrument Serif (`instrument-serif`), DM Serif Display (`dm-serif-display`), Young Serif (`young-serif`)
- **Handwritten:** Caveat (`caveat`), Pacifico (`pacifico`), Permanent Marker (`permanent-marker`)
- **Mono:** JetBrains Mono (`jetbrains-mono`), Space Mono (`space-mono`)

## Icons

199 icons, for badges, features and icon rows.

- **General:** `trophy`, `crown`, `medal`, `star`, `heart`, `sparkle`, `fire`, `rocket`, `target`, `flag`, `gift`, `thumbs-up`, `verified`, `check`, `confetti`, `smiley`, `lightbulb`, `magic-wand`, `infinity`, `bolt`
- **Privacy:** `shield`, `shield-check`, `lock`, `key`, `fingerprint`, `eye`, `eye-off`, `incognito`, `block`, `hand`
- **Time:** `clock`, `timer`, `hourglass`, `countdown`, `calendar`, `calendar-check`, `alarm`, `bell`, `bell-off`, `moon`, `sun`
- **Media:** `camera`, `image`, `video`, `video-off`, `play`, `film`, `music`, `mic`, `headphones`, `speaker`, `mute`
- **People:** `user`, `users`, `chat`, `chats`, `mail`, `phone`, `share`, `link`, `globe`, `translate`, `megaphone`
- **Work:** `chart-bar`, `chart-line`, `chart-pie`, `trend-up`, `search`, `sliders`, `gear`, `filter`, `checklist`, `check-square`, `note`, `pencil`, `book`, `books`, `bookmark`, `tag`, `folder`, `document`, `clipboard`, `calculator`, `database`, `newspaper`
- **Devices:** `iphone`, `devices`, `watch`, `desktop`, `keyboard`, `widgets`, `layers`, `cloud`, `cloud-check`, `sync`, `download`, `upload`, `export`, `offline`, `wifi`, `battery`, `chip`, `tap`, `cursor`, `qr-code`, `scan`, `code`, `terminal`, `plug`, `power`, `toggle`, `robot`, `palette`, `brush`
- **Places:** `map-pin`, `map`, `compass`, `navigation`, `airplane`, `car`, `bicycle`, `house`, `storefront`, `mountains`, `tree`
- **Money:** `wallet`, `credit-card`, `coins`, `dollar`, `cart`, `bank`, `piggy-bank`, `receipt`, `ticket`
- **Life:** `heartbeat`, `barbell`, `run`, `leaf`, `plant`, `drop`, `brain`, `bed`, `pill`, `food`, `coffee`, `lotus`, `paw`, `graduation-cap`, `puzzle`, `game`
- **Signs:** `plus`, `close`, `question`, `info`, `warning`, `thumbs-down`

**Brand logos** (43): `youtube`, `instagram`, `tiktok`, `facebook`, `snapchat`, `x`, `threads`, `reddit`, `whatsapp`, `telegram`, `messenger`, `discord`, `twitch`, `pinterest`, `linkedin`, `spotify`, `slack`, `notion`, `github`, `twitter`, `mastodon`, `tumblr`, `medium`, `patreon`, `wechat`, `soundcloud`, `apple-podcasts`, `steam`, `meta`, `apple`, `google`, `android`, `app-store`, `google-play`, `google-drive`, `dropbox`, `figma`, `amazon`, `paypal`, `stripe`, `openai`, `windows`, `teams`

A brand logo is somebody's trademark. See [What App Review accepts](/docs/app-review#other-companies-names-and-logos).

---

# Header and search results

Source: https://shots.beffa.xyz/docs/store-assets

Besides screenshots, App Store Connect takes wide images on its **Header and Search Results** tab: one shown at the top of the product page, one shown with the app in search results. appShots makes both from the screenshots, theme and fonts the project has already.

They are optional. A project has none until you add one.

For a wide picture outside the store, such as the top of a README, see [Banners](/docs/banners).

## The three assets

| Asset | Pixels | Safe area | Used for |
| --- | --- | --- | --- |
| `header` | 3840 × 1646 (21:9) | 1646 × 661 at 1097, 493 | Shown at the top of the product page. |
| `search` | 3840 × 2560 (3:2) | 2168 × 1030 at 836, 765 | Shown with the app in search results. |
| `universal` | 5244 × 2950 (16:9) | 1402 × 962 at 1921, 660 | One file that App Store Connect crops for both places. |

Make `header` and `search`, or one `universal` file that App Store Connect crops for both places.

## The safe area

Each device crops these images differently. Only the **safe area**, a rectangle in the middle, is shown everywhere. The table gives its size and where its top left corner is.

- Copy and the main subject go inside it. appShots places the copy there for you.
- The rest of the canvas is background and devices that may be cut off.
- **Show safe area** in the Studio, and the previews an agent gets, draw it as a dashed frame. The frame is never in the exported file.

## Layouts

| Layout | What it is |
| --- | --- |
| `fan` | Copy at the top of the safe area, up to five devices fanned out below it and bleeding off the bottom edge. The first screenshot takes the centre. |
| `sides` | Copy in the middle of the safe area, up to four devices standing left and right of it. With one screenshot the device stands on the right. |
| `wall` | Copy over a dimmed, tilted wall of bare screenshots that fills the whole canvas. |
| `split` | Copy in a column on the left, up to three devices on the right. Made for banners. On an App Store asset the copy would leave the safe area, so it is drawn as sides there. |

`fan` shows up to five screenshots, `sides` up to four and `wall` up to six. Further ones are left out. `split` is the layout of [banners](/docs/banners), where nothing is cropped.

## Fields

```json
{
  "header": {
    "layout": "fan",
    "headline": "Build *habits* that stick",
    "screenshots": ["home", "stats", "detail"]
  }
}
```

| Field | What it is |
| --- | --- |
| `layout` | `fan`, `sides` or `wall`. Default `fan` |
| `eyebrow`, `headline`, `subheadline` | The copy, as on a slide. Keep it to one short phrase |
| `screenshots` | Up to six names of uploaded screenshots, most important first. Without any, the asset is copy on the background |
| `badges` | Up to three, as on a slide |
| `badgePosition` | `top` or `bottom` |
| `logo` | Your app's own logo or icon. See [Logos and artwork](/docs/images) |
| `background`, `textColor`, `accentColor`, `font`, `textScale` | As on a slide. Left out, the project's theme decides |
| `device` | `frame`, `color`, `scale` and `shadow` |

The copy is always centred, because it has to stay inside the safe area.

## Make them

**In the Studio:** the **Header and search results** section of the project page adds, previews and edits each one.

**Through an agent:** `set_creative_assets`, then `render`:

```json
{ "project": "streaks", "creatives": ["header", "search"] }
```

Each asset you pass replaces that one, and the others stay. `"remove": ["universal"]` deletes one.

## Export

They are rendered with the slides and land in the ZIP as:

```
en-US/creative-assets/header.png
en-US/creative-assets/search.png
en-US/creative-assets/universal.png
```

One per language. See [Render and export](/docs/export).

## Rules

- No other company's logo, and no icon row. Apple does not allow them here.
- One short phrase of copy. These images are seen small.
- Real figures only in badges.

See [What App Review accepts](/docs/app-review#header-and-search-results-images).

---

# Sizes

Source: https://shots.beffa.xyz/docs/sizes

You design a set once per device: once for the iPhone, once for the iPhone Duo, once for the iPad and once for the Mac. The export then holds that design at the exact pixel size of every display App Store Connect lists, each in a folder of its own.

Every file is a PNG without transparency. iPhone and iPad sets are portrait, and a Mac set is landscape.

## Screenshots

| Folder | Pixels | Display in App Store Connect | Exported |
| --- | --- | --- | --- |
| `iphone-6.9` | 1320 × 2868 | iPhone with Dynamic Island (large display) | Always |
| `iphone-6.3` | 1206 × 2622 | iPhone with Dynamic Island (medium display) | Always |
| `iphone-6.5` | 1284 × 2778 | iPhone with Face ID (large display) | Always |
| `iphone-6.1` | 1170 × 2532 | iPhone with Face ID (medium display) | Always |
| `iphone-5.5` | 1242 × 2208 | iPhone with Home Button (large display) | When you ask |
| `iphone-4.7` | 750 × 1334 | iPhone with Home Button (medium display) | When you ask |
| `iphone-4` | 640 × 1136 | iPhone with Home Button (4" display) | When you ask |
| `iphone-3.5` | 640 × 960 | iPhone with Home Button (3.5" display) | When you ask |
| `iphone-duo` | 2007 × 2853 | iPhone Duo | Always |
| `iphone-duo-cover` | 1398 × 2034 | iPhone Duo | When you ask |
| `ipad-13` | 2064 × 2752 | iPad 13" display | Always |
| `ipad-11` | 1668 × 2420 | iPad 11" display | Always |
| `ipad-12.9` | 2048 × 2732 | iPad Pro (2nd generation) 12.9" display | When you ask |
| `ipad-10.5` | 1668 × 2224 | iPad 10.5" display | When you ask |
| `ipad-9.7` | 1536 × 2048 | iPad 9.7" display | When you ask |
| `mac-2880` | 2880 × 1800 | Mac | Always |
| `mac-2560` | 2560 × 1600 | Mac | When you ask |
| `mac-1440` | 1440 × 900 | Mac | When you ask |
| `mac-1280` | 1280 × 800 | Mac | When you ask |

The folder name is also the id of the size, which agents and the API use.

## What an export holds

The sizes marked **Always**, for every device the project has screenshots of:

| Your project has | The export holds |
| --- | --- |
| iPhone screenshots | `iphone-6.9`, `iphone-6.3`, `iphone-6.5`, `iphone-6.1` |
| iPhone Duo screenshots as well | `iphone-duo` too |
| iPad screenshots as well | `ipad-13` and `ipad-11` too |
| Mac screenshots as well | `mac-2880` too |

A project without any screenshots yet counts as an iPhone project.

The other sizes are for displays Apple no longer sells, and the three smaller sizes the Mac App Store takes besides its largest. App Store Connect fills a display you leave empty from a larger one, and a Mac app needs one size only, so most apps never need them. To add or drop sizes, tick them under **Project → Export sizes** in the Studio, or set the project's `targets`:

```json
{ "project": "streaks", "targets": ["iphone-6.9", "iphone-6.3", "iphone-5.5"] }
```

An empty list goes back to the standard sizes.

## One design, many sizes

Displays of the same shape get the same picture. The slide is drawn once at the largest size and scaled to the others, so a headline breaks on the same word and the device sits in the same place on every one of them.

| Drawn | Scaled from it |
| --- | --- |
| `iphone-6.9` | `iphone-6.3`, `iphone-6.5`, `iphone-6.1` |
| `iphone-5.5` | `iphone-4.7`, `iphone-4` |
| `ipad-13` | `ipad-12.9`, `ipad-10.5`, `ipad-9.7` |
| `mac-2880` | `mac-2560`, `mac-1440`, `mac-1280` |

`iphone-3.5`, `iphone-duo`, `iphone-duo-cover` and `ipad-11` have a shape of their own and are drawn on their own. The layout is the same and follows the shape.

## Which device a screenshot counts as

Each raw screenshot belongs to one device:

| The image is | It counts as |
| --- | --- |
| Exactly 2007 × 2853 or 1398 × 2034 | iPhone Duo |
| Wider than it is tall, such as 2880 × 1800 | Mac |
| A captured Mac window, which has see-through round corners | Mac, whatever its shape |
| Tall and narrow, such as 1320 × 2868 | iPhone |
| Tall and wider than that, such as 2064 × 2752 | iPad |

A picture at the exact size of an iPhone or iPad screenshot stays with that device when it is turned on its side.

The iPhone Duo is told by its exact size because its shape is close to an iPad's. When a screenshot lands under the wrong device, change it with the small menu on its thumbnail under **Assets** in the Studio, or say it at upload: `appshots upload streaks home.png --as duo`. `--as` takes `iphone`, `duo`, `ipad` and `mac`.

Upload each device's screenshot under the same name. A slide that uses `home` then shows the iPhone `home` on iPhone sizes, the Duo `home` on Duo sizes, the iPad `home` on iPad sizes and the Mac `home` on the Mac size. If a device has none of its own, the closest other one stands in, and the render says so.

## Raw screenshots

Any resolution from 300 pixels on the short side works. The screenshot is scaled to the device's screen. For the sharpest result, capture on the simulators the largest sizes are made for: an iPhone Pro Max, the iPhone Duo and the 13-inch iPad Pro. `appshots run` picks them for you. A Mac app is captured from its own window. See [Mac apps](/docs/mac).

## Header and search results

| Asset | Pixels | Safe area | Used for |
| --- | --- | --- | --- |
| `header` | 3840 × 1646 (21:9) | 1646 × 661 at 1097, 493 | Shown at the top of the product page. |
| `search` | 3840 × 2560 (3:2) | 2168 × 1030 at 836, 765 | Shown with the app in search results. |
| `universal` | 5244 × 2950 (16:9) | 1402 × 962 at 1921, 660 | One file that App Store Connect crops for both places. |

These are the largest sizes App Store Connect accepts for them. See [Header and search results](/docs/store-assets).

## Banners

| Banner | Pixels | Used for |
| --- | --- | --- |
| `readme` | 2400 × 800 (3:1) | The wide picture at the top of a README or a web page. |
| `social` | 1280 × 640 (2:1) | The picture a link to a GitHub repository shows, at the size GitHub asks for. |
| `link` | 1200 × 630 (1.91:1) | The picture a link to a web page shows in chats and feeds: its og:image. |
| `post` | 1920 × 1080 (16:9) | For a post, a slide of a deck or a video thumbnail. |

Wide pictures for outside the store. See [Banners](/docs/banners).

## What appShots does not make

Landscape screenshots of an iPhone or iPad app, app previews (videos), and Apple Watch, Apple TV or Vision Pro sizes.

---

# The appshots command

Source: https://shots.beffa.xyz/docs/cli

`appshots` is one small Node.js file with no dependencies. It runs your app in a simulator, captures screens from the simulator, a device, a Mac window or a web page, moves files between your Mac and the server, and can call every tool the server has. The [setup command](/docs/connect) installs it as `~/.local/bin/appshots`.

It needs Node.js 18 or newer. The simulator and plugged-in devices need Xcode. A web app's pages need Chrome, Edge or Brave instead.

## Set up and keep current

| Command | What it does |
| --- | --- |
| `appshots setup <url> [--agent <agent>]` | Saves the server and your token, installs the skill and this command, and connects the agent. Agents: `claude` (the default), `codex`, `opencode`, `pi`, `other`. `--token <token>` skips the question |
| `appshots login <url>` | Saves the server address and the token only |
| `appshots update` | Brings the installed skill and this command up to date with the server's. Needs no token |
| `appshots check` | Reports the server, whether the skill is current, Xcode, the project in this folder, what it is made with and the commands that start it, simulators, installed apps and devices. Every line says what to do next |

When the server has a newer skill or command than this Mac, every other command says so in one line when it is done. See [Connect an agent](/docs/connect) and [Update](/docs/update).

## Run the app

```bash
appshots run
appshots run --ipad --dark --lang de
appshots run --duo
appshots run --capture home --to streaks
```

`run` builds the Xcode project in the current folder and starts the app in a simulator. It is for iPhone and iPad apps. A Mac app runs on the Mac itself: start it your own way and use `capture --mac`. See [Mac apps](/docs/mac). An app made with Flutter, React Native, Expo, Capacitor or Tauri is started by its own command, after `appshots boot`. See [Web apps and other toolkits](/docs/web-apps).

| Option | What it does |
| --- | --- |
| `--scheme <name>` | The scheme to build, when the choice is not obvious |
| `--project <path>` | The workspace or project to build, when there are several |
| `--fresh` | Builds in a folder of its own instead of reusing Xcode's build |
| `--ipad` | On the 13-inch iPad Pro instead of the iPhone Pro Max |
| `--duo` | On the iPhone Duo instead. Needs an Xcode that has its simulator |
| `--sim <name or udid>` | On this simulator instead. It is booted if need be |
| `--dark`, `--light` | Sets the simulator's appearance first |
| `--lang <code>` | Starts the app in this language, such as `de` or `pt-BR`. A screenshot taken with `--capture` is filed under that language in the project |
| `--capture <name>` | Takes the screenshot as well, with a clean status bar |
| `--to <project>` | Uploads that screenshot to this project |
| `--wait <seconds>` | How long the app gets to settle before the capture. Default 2 |
| `-- <arguments>` | Handed to the app as they are, such as `-- -appshots stats` |

```bash
appshots launch com.example.streaks --capture stats --to streaks -- -appshots stats
```

`launch <bundle id>` starts an installed app again without building, to switch screen or language. It takes `--sim`, `--dark`, `--light`, `--lang`, `--capture`, `--to`, `--as` and `--wait`.

`appshots boot` boots the simulator the store sizes ask for, without building anything, and prints its udid together with the run command of the toolkit in the current folder, such as `flutter run -d <udid>`. It takes `--ipad`, `--duo` and `--sim`.

`appshots simctl <arguments>` runs `xcrun simctl` with the right Xcode, for `openurl`, `ui`, `privacy` and `push`.

## Capture

| Command | What it does |
| --- | --- |
| `appshots devices` | Lists booted simulators and connected devices |
| `appshots capture` | Takes one screenshot |
| `appshots shoot <project> <names…>` | Names each screen in turn, waits for Return while you open it, then captures and uploads |

Options of `capture`, which `shoot` takes too:

| Option | What it does |
| --- | --- |
| `--name <name>` | The name slides refer to. Default: a timestamp |
| `--sim [name or udid]` | From a booted simulator. This is the default |
| `--device [name or udid]` | From a connected, unlocked iPhone or iPad. Needs Xcode 27 or newer |
| `--mac <app>` | From a Mac app running on this Mac: its frontmost window, without the shadow. `<app>` is the app's name. The terminal needs the Screen Recording permission. See [Mac apps](/docs/mac) |
| `--web <address>` | From a web app: the page at this address, drawn by Chrome without a window at the size of an iPhone. `--as ipad`, `--as mac` and `--as duo` draw it at those sizes. Not for `shoot`. See [Web apps and other toolkits](/docs/web-apps) |
| `--clean` | Simulator only: shows 9:41, full battery and full signal |
| `--project <id>` | Uploads it to this project straight away. `--to` works too |
| `--as <iphone, duo, ipad or mac>` | Files it under this device. Only needed when the server cannot tell from the size. `upload`, `run` and `launch` take it too |
| `--lang <code>` | The language the screenshot shows the app in, one the project has. Left out, it is for the project's first language. See [Languages](/docs/languages#screenshots-per-language) |
| `--out <dir>` | Where the PNG is saved. Default `./raw-shots` |

With `--web`:

| Option | What it does |
| --- | --- |
| `--dark`, `--light` | The colour scheme the page is asked for |
| `--wait <seconds>` | Time the page gets to settle once it has loaded. Default 1 |
| `--init <script or @file>` | JavaScript that runs before the page's own on every load: sample data in `localStorage`, a fixed date |
| `--eval <script or @file>` | JavaScript that runs in the loaded page before the picture: the click or scroll that brings up the screen |
| `--viewport <width>x<height>` | The page's size in points instead of the device's |
| `--profile <dir>` | Keeps cookies and storage in this folder between captures |

## Upload and download

```bash
appshots upload streaks ./screens
appshots upload streaks ./screens-de --lang de-DE
appshots upload streaks logo.svg --image
appshots pull streaks --render
```

| Command | What it does |
| --- | --- |
| `appshots projects` | Lists your projects |
| `appshots upload <project> <files or folders…>` | Uploads screenshots. The name is the file name, or `--name` for a single file. `--as duo` or `--as mac` files them under that device. `--lang de-DE` files them under that language. `--image` uploads logos and artwork instead |
| `appshots pull <project> [--out <dir>]` | Downloads the rendered set into `./store-screenshots/<project>`, one folder per display of App Store Connect, with the header and search results images and the [banners](/docs/banners). `--render` renders first |
| `appshots mockups <project>` | Downloads every screenshot in its device on a transparent background. See [Mockups](#mockups) |
| `appshots open [project]` | Opens the Studio in the browser |

## Mockups

```bash
appshots mockups streaks
appshots mockups streaks --style clay --color silver --shadow soft --rotate -8
appshots mockups streaks --width 1200 --out ./website/img
```

`mockups` saves one transparent PNG per screenshot into `./mockups/<project>`, named `<screenshot>-<device>.png`. It needs no slides and uses no render. What the options set is saved with the project, so the Studio's Mockups page shows the same, and the next call without options draws them the same way.

| Option | What it does |
| --- | --- |
| `--out <dir>` | Where the files go. Default `./mockups/<project>` |
| `--lang <code>` | Only this language's. Left out: the first language's, and a folder per other language for the screenshots it has of its own |
| `--style <look>` | `real`, `clay`, `outline` or `sketch` |
| `--color <colour>` | `black`, `silver`, `natural`, `gold`, `blue` or `orange` |
| `--shadow <kind>` | `none`, `soft` or `strong` |
| `--rotate <degrees>` | The tilt, such as `-8` |
| `--perspective <side>` | A 3D turn: `none`, `left` or `right` |
| `--status-bar <kind>`, `--time <9:41>` | A clean status bar (`none`, `cover` or `overlay`) and the time it shows |
| `--frame <frame>` | `none` for the bare screenshot with rounded corners |
| `--width <pixels>` | The width of each PNG. `auto`, the default, shows the screenshot pixel for pixel |

See [Mockups](/docs/mockups).

## Call the server's tools

Everything an agent can do through MCP works from the command line as well, which is how agents without MCP use appShots.

```bash
appshots tools
appshots tools set_slides
appshots call get_design_guide
appshots call render '{"project":"streaks","slides":[3]}'
appshots call set_slides @slides.json
```

Arguments are JSON: inline, `@file.json`, or `-` for standard input. Pictures a tool returns are saved in a temporary folder, or in the folder after `--out`, and the command prints where. See [MCP tools](/docs/mcp).

## Where the settings live

| | |
| --- | --- |
| Server and token | `~/.config/appshots/config.json`, readable by you only |
| Or, for one command | The environment variables `APPSHOTS_URL` and `APPSHOTS_TOKEN` |
| The skill | `~/.claude/skills/appshots` for Claude Code, `~/.agents/skills/appshots` for the others |

`appshots help` prints all of this.

---

# MCP tools

Source: https://shots.beffa.xyz/docs/mcp

appShots is a remote MCP server. An agent connects to it once and can then build and render sets by itself.

## The endpoint

| | |
| --- | --- |
| Address | `https://shots.beffa.xyz/mcp` |
| Transport | Streamable HTTP, `POST` only |
| Sign-in | The header `Authorization: Bearer <token>`, with a token from **Connect an agent** in the Studio |
| Sessions | None. Every request stands alone |

There is no OAuth, so add it with a header. Pasting the address into a connector dialog that expects a sign-in page does not work. The exact commands for each agent are in [Connect an agent](/docs/connect#only-the-mcp-connection).

A token reaches the projects of the account it belongs to and nothing else. See [Accounts, tokens and limits](/docs/accounts).

## The order of work

1. `get_design_guide`, once, before anything else.
2. `create_project`, or `list_projects` and `get_project` to continue one.
3. Upload the raw screenshots. This goes through the [command line](/docs/cli) or the [HTTP API](/docs/api), because MCP has no way to send files. A project in several languages can hold each screenshot once per language.
4. `set_slides`. For an app on more than one device, once more for each of the others with `device`, so every device has a set composed for its own canvas.
5. `render`, and look at the pictures it returns.
6. `update_slide` for the fixes, then `render` again.

Every `render` uses one of the account's renders, whatever it draws. So an agent writes the whole set first, renders it in one call, fixes everything it sees in one go and renders once more. The design guide tells it how many are left, and so does every `render`. See [Render and export](/docs/export#how-many-renders).

## Tools

### get_design_guide

Returns the guide the server is built around: the workflow, how to upload screenshots, every layout, background, font, device option and badge, what differs for a Mac app, and the rules for header and search results images and banners. No arguments. It always matches the server's version.

### list_projects

Lists the projects of the account: id, name, number of slides, languages and when each last changed. `ownSets` names the devices that have a slide set of their own. No arguments.

### create_project

| Argument | What it is |
| --- | --- |
| `name` | The app's name. Required |
| `locales` | The languages. Default `["en"]` |
| `theme` | The look every slide shares. See [Slides](/docs/slides#the-look) |

Returns the project's id and how to upload screenshots into it.

### get_project

`project` is the id. Returns the theme, the slides, the sets single devices have of their own under `deviceSlides`, the header and search results images and banners, the screenshots and images uploaded so far, and `exportSizes`, the sizes a render writes. Each screenshot is listed with its `device`, and with its `locale` when it was uploaded for one language only. A Mac screenshot also says whether it is `shown` as a `window` or inside a `MacBook`. See [Languages](/docs/languages#screenshots-per-language).

### update_project

Changes `name`, `locales`, `targets` or `theme` of `project`. The theme is replaced as a whole, so send all of it. `targets` is the list of sizes the project exports, and `[]` goes back to the standard ones. See [Sizes](/docs/sizes).

### set_slides

| Argument | What it is |
| --- | --- |
| `project` | The id |
| `slides` | One to ten slides, in store order. See [Slides](/docs/slides) |
| `device` | `iphone`, `duo`, `ipad` or `mac`: the set is for this device alone, which then draws it in place of the shared one. Left out, the shared set is replaced |
| `theme` | Replaces the project's theme when given |

Replaces the whole set. It names screenshots that slides refer to but that are not uploaded yet.

A project starts with one shared set that every device draws on its own canvas. An agent designs the iPhone's first and then gives the iPad and the Mac a set of their own, with the same story recomposed for the wider canvas:

```json
{ "project": "streaks", "device": "ipad", "slides": [{ "layout": "device-right", "screenshot": "home", "headline": "Never miss a *day*" }] }
```

With `device` and an empty `slides` list, the device's own set is removed and it shows the shared one again. See [A set for each device](/docs/slides#a-set-for-each-device).

### update_slide

| Argument | What it is |
| --- | --- |
| `project` | The id |
| `slide` | The slide's number, starting at 1 |
| `of` | `iphone`, `duo`, `ipad` or `mac`: the slide is in this device's own set. Left out, it is in the shared set |
| `changes` | Fields to set. They are merged over the slide, `device` included |
| `remove` | Names of fields to clear, such as `["subheadline", "badges"]` |

```json
{ "project": "streaks", "slide": 3, "changes": { "headline": "See your *progress*", "device": { "offsetY": 6 } } }
```

### set_creative_assets

Sets the wide pictures of `project`: `header`, `search` or `universal` for App Store Connect, and the banners `readme`, `social`, `link` and `post` for outside the store. Each one given replaces that picture, and the others stay. `remove` deletes them by name. See [Header and search results](/docs/store-assets) and [Banners](/docs/banners).

### render

| Argument | What it is |
| --- | --- |
| `project` | The id |
| `slides` | Slide numbers to render, starting at 1. Left out: all |
| `creatives` | Header and search results images and banners to render. Left out: all of them, along with the slides. Given alone: the slides are left as they are. `[]` skips them |
| `targets` | Sizes for this render only. Left out: every size the project exports, which is the standard sizes of each device it has screenshots of. See [Sizes](/docs/sizes) |
| `locales` | Languages. Default: all of the project's |

Returns a contact sheet per device as a picture, a preview of each wide image, the store's with their safe area marked, a link to every PNG, a link to the ZIP, warnings such as a missing screenshot, and how many renders the account has left.

### render_mockups

Draws each raw screenshot in its device with nothing behind it: one transparent PNG per screenshot, for a website, a README or a deck. It needs no slides and uses no render.

| Argument | What it is |
| --- | --- |
| `project` | The id |
| `screenshots` | Names of the screenshots to draw. Left out: all of them |
| `locale` | The language whose screenshots to draw. Default: the first |
| `device` | How every mockup is drawn: `frame`, `style`, `color`, `shadow`, `rotate`, `perspective`, `statusBar`, `statusBarTime`, `statusBarColor`. Given, it is laid over what the project had |
| `width` | The width of each PNG in pixels. Left out: the screenshot pixel for pixel. `null` goes back to that |

```json
{ "project": "streaks", "device": { "style": "clay", "shadow": "soft", "rotate": -8 }, "width": 1200 }
```

What is given is saved with the project, so the Studio's Mockups page shows the same. Returns a link to every PNG, a link to the ZIP and a picture of the mockups to check. See [Mockups](/docs/mockups).

## Try it without an agent

```bash
appshots tools
appshots call list_projects
```

Or with curl:

```bash
curl -s https://shots.beffa.xyz/mcp \
  -H "Authorization: Bearer $APPSHOTS_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
```

## Errors

A tool that cannot do what was asked answers with a plain sentence saying why: an unknown project, a slide that does not exist, a value outside its range. A missing or revoked token gets HTTP 401. See [Troubleshooting](/docs/troubleshooting).

---

# HTTP API

Source: https://shots.beffa.xyz/docs/api

Everything the Studio does goes through a small JSON API. Use it from a script or from CI. For an agent, [MCP](/docs/mcp) is the better fit.

## Basics

| | |
| --- | --- |
| Base address | `https://shots.beffa.xyz/api` |
| Sign-in | The header `Authorization: Bearer <token>` |
| Bodies | JSON, except file uploads, which send the file itself as the body |
| Errors | `{ "error": "A sentence saying why" }` with status 400, 401, 403 or 404 |

Create the token under **Connect an agent** in the Studio. The examples expect it in `APPSHOTS_TOKEN`.

## Projects

| Request | What it does |
| --- | --- |
| `GET /api/projects` | Lists your projects |
| `POST /api/projects` | Creates one. Body: `name`, and optionally `locales`, `targets`, `theme`, `slides`, `deviceSlides`, `creatives` |
| `GET /api/projects/:id` | Returns the project with its screenshots, images and rendered files |
| `PUT /api/projects/:id` | Changes any of `name`, `locales`, `targets`, `theme`, `slides`, `deviceSlides`, `creatives`, `mockups`. Each one given is replaced as a whole |
| `DELETE /api/projects/:id` | Deletes it with everything in it |
| `GET /api/usage` | What the account keeps on the server: the number of `projects`, `slides`, `shots` and `images`, and their size in `bytes` |

```bash
curl -s https://shots.beffa.xyz/api/projects \
  -H "Authorization: Bearer $APPSHOTS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"Streaks","locales":["en-US","de-DE"]}'
```

The answer holds the project's `id`, which every other request uses. The shape of `slides`, `theme` and `creatives` is on [Slides](/docs/slides), [Header and search results](/docs/store-assets) and [Banners](/docs/banners). `deviceSlides` holds the sets single devices have of their own, by `iphone`, `duo`, `ipad` and `mac`, such as `{ "ipad": [ … ] }`. A device that is not in it draws `slides`. See [A set for each device](/docs/slides#a-set-for-each-device). `creatives` holds both, by name: `header`, `search`, `universal`, `readme`, `social`, `link`, `post`. `targets` is the list of sizes the project exports, such as `["iphone-6.9","iphone-5.5"]`. Leave it out, or send `null` or `[]`, for the standard sizes. See [Sizes](/docs/sizes).

## Screenshots

```bash
curl -s -X PUT https://shots.beffa.xyz/api/projects/streaks/shots/home \
  -H "Authorization: Bearer $APPSHOTS_TOKEN" \
  --data-binary @home.png
```

| Request | What it does |
| --- | --- |
| `PUT /api/projects/:id/shots/:name` | Uploads a raw screenshot under that name. PNG, JPEG or WebP, up to 40 MB. An existing one with that name, device and language is replaced |
| `PATCH /api/projects/:id/shots/:file` | Files one under another device, another language, or both. Body: `{ "device": "ipad" }`, `{ "locale": "de-DE" }`, or `{ "locale": null }` for the first language. Replaces what was there under the name |
| `DELETE /api/projects/:id/shots/:file` | Deletes one. `:file` is the `file` value from the project's `shots` list |

iPhone, iPhone Duo, iPad or Mac is read from the size and shape of the image. Add `?device=iphone`, `?device=duo`, `?device=ipad` or `?device=mac` to the upload to say it yourself. See [Sizes](/docs/sizes#which-device-a-screenshot-counts-as).

A captured Mac window is stored without the shadow macOS adds, so its `width` and `height` in the `shots` list are those of the window. Its entry has `corner`, the radius of its round corners in pixels. See [Mac apps](/docs/mac).

A screenshot is for the project's first language unless you add `?locale=de-DE`. The language has to be one of the project's `locales`, and `de` finds `de-DE`. Each entry of the project's `shots` list says which `locale` it is for; the first language's have none. See [Languages](/docs/languages#screenshots-per-language).

## Logos and images

| Request | What it does |
| --- | --- |
| `PUT /api/projects/:id/images/:name` | Uploads a logo or artwork. PNG, SVG, JPEG or WebP, up to 20 MB |
| `DELETE /api/projects/:id/images/:file` | Deletes one |

## Render and download

```bash
curl -s -X POST https://shots.beffa.xyz/api/projects/streaks/render \
  -H "Authorization: Bearer $APPSHOTS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"locales":["en-US"]}'
```

| Request | What it does |
| --- | --- |
| `POST /api/projects/:id/render` | Renders and answers when it is done. Body, all optional: `targets`, `locales`, `slides`, `creatives`, `background`. Without `targets`, every size the project exports |
| `GET /api/renders` | Your renders that are waiting, running or ended in the last hour, newest first, and `rendersLeft` |
| `GET /api/renders/:job` | One of them |
| `DELETE /api/renders/:job` | Cancels one that is waiting or running |
| `GET /api/projects/:id/export.zip` | The files of the last render as a ZIP. `?render=1` renders everything first |
| `GET /files/:id/out/<language>/<size>/<number>.png` | One rendered file |
| `GET /files/:id/out/<language>/creative-assets/<name>.png` | One header or search results image |
| `GET /files/:id/out/<language>/banners/<name>.png` | One banner |

**`slides` counts from 0 here**, unlike the MCP tool, which counts from 1.

The whole set in one request:

```bash
curl -s -o streaks.zip "https://shots.beffa.xyz/api/projects/streaks/export.zip?render=1" \
  -H "Authorization: Bearer $APPSHOTS_TOKEN"
```

Rendering takes a while, so give the request a generous timeout. See [Render and export](/docs/export) for the layout of the ZIP.

### Without waiting

With `"background": true` the request answers at once, with status 202 and the render as a job:

```bash
curl -s -X POST https://shots.beffa.xyz/api/projects/streaks/render \
  -H "Authorization: Bearer $APPSHOTS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"background":true}'
```

```json
{ "job": { "id": "5f0c…", "project": "streaks", "name": "Streaks", "state": "queued", "done": 0, "total": 0 }, "rendersLeft": 6 }
```

Ask `GET /api/renders/<id>` until `state` is no longer `queued` or `running`. It ends as `done`, `failed` (with `error`) or `cancelled`. `done` of `total` is how many pictures are drawn so far. Then fetch `export.zip`. The render draws the project as it was when you asked, so changes made while it runs are not in it.

### Counted renders

Every answer to a render carries `rendersLeft`: how many the account has left, or `null` where they are not counted. Each render uses one, and one that fails or is cancelled uses none. At zero a render is refused with status 400. See [Render and export](/docs/export#how-many-renders).

## Mockups

Each screenshot in its device on a transparent background. They are drawn when they are asked for and use no render. See [Mockups](/docs/mockups).

```bash
curl -s -o home-iphone.png "https://shots.beffa.xyz/api/projects/streaks/mockups/home.iphone.png?width=1200" \
  -H "Authorization: Bearer $APPSHOTS_TOKEN"
```

| Request | What it does |
| --- | --- |
| `GET /api/projects/:id/mockups` | Lists them: `settings`, and per mockup its `name`, `device`, the `png` address and the name to save it as. `?locale=de-DE` for another language |
| `GET /api/projects/:id/mockups/:file` | One as a PNG. `:file` is the `file` value of a screenshot. `?width=1200` sets the width for this request, and `?download=1` sends it as a download |
| `GET /api/projects/:id/mockups.zip` | All of them as a ZIP. `?locale=de-DE` for one language only |

How the device is drawn is the project's `mockups` value, set with `PUT /api/projects/:id`:

```json
{ "mockups": { "device": { "style": "clay", "color": "silver", "shadow": "soft", "rotate": -8 }, "width": 1200 } }
```

`device` takes `frame`, `style`, `color`, `shadow`, `rotate`, `perspective`, `statusBar`, `statusBarTime` and `statusBarColor`, as on [Slides](/docs/slides#device). Without `width`, the screenshot is shown pixel for pixel. To try settings without saving them, add them to a request as `?settings=<the same JSON, URL-encoded>`.

## What a token cannot do

Creating and revoking tokens, deleting the account and managing who can sign in need a signed-in browser. A token that leaks can therefore not make more tokens.

## Public addresses

These need no token:

| Address | What it is |
| --- | --- |
| `/healthz` | Answers when the server is up |
| `/skill.md` | The skill, as the server hands it out |
| `/skill.json` | A short hash of the skill and one of the command, which `appshots` compares its own copies with |
| `/install.sh` | The setup script |
| `/cli/appshots.mjs` | The `appshots` command |
| `/docs/<page>.md` | Any page of these docs as Markdown |
| `/llms.txt`, `/llms-full.txt` | The docs as an index, and in one file, for language models |

---

# Accounts, tokens and limits

Source: https://shots.beffa.xyz/docs/accounts

## Sign in

Sign in with a Google account on the [home page](/home). There is no password to set, and appShots never sees your Google password. It receives your email address and your name from Google, and nothing else. Google must have verified the address.

Your projects are yours alone. No account can see another's.

You stay signed in on that browser for 30 days after your last visit, so you are not signed out while you keep coming back. The [home page](/home) knows you too and takes you straight to your projects. **Sign out** under **Settings** ends it.

## Who can sign in

The person who runs the server decides, under **Admin** in the Studio:

- **Anyone with a Google account**, or
- **Only the people listed there**, by email address.

If the home page says your Google account is not on the list, ask whoever runs the server to add your address.

## API tokens

A token is what lets your agent, the `appshots` command or a script act as you. It starts with `appshots_`.

**Create one** under **Connect an agent** in the Studio, with **Create token**. It is shown once, inside the setup command. Run that command, and the token is saved on your Mac. See [Connect an agent](/docs/connect).

**See them** under **Settings**: the last four characters of each, the day it was created, and the day it was last used.

**Revoke one** with **Revoke** beside it. It stops working at once, everywhere it was used. Then create a new one and run the setup command again.

Good to know:

- The server stores a fingerprint of each token, not the token. A lost token cannot be shown again. Make a new one.
- A token reaches your projects, screenshots and renders. It cannot create or revoke tokens, delete your account or manage people. Those need a signed-in browser.
- Treat it like a password. Do not commit it, and do not paste it into a chat.
- An account holds up to 20 tokens. One per Mac or per agent makes it easy to revoke the right one.

## Limits

| | Limit |
| --- | --- |
| Projects per account | 5 |
| Screenshots per project | 30 for each language |
| Logos and images per project | 30 |
| Slides per set | 10 |
| Languages per project | 40 |
| A screenshot file | 40 MB |
| An image file | 20 MB |
| Renders | 10 per account. See below |
| Renders at once | One running and one waiting per account |

The person who runs the server has no limit on projects, screenshots, images and renders.

## Renders

An account gets 10 renders. Every export uses one, whether it comes from **Export ZIP**, from `appshots pull --render` or from an agent's `render` tool, and whatever it draws. Previews in the Studio are free, and a render that fails or is cancelled uses none. See [Render and export](/docs/export#how-many-renders).

The Studio shows how many are left beside **Renders** in the sidebar and under **Settings**.

**To get more**, ask the person who runs the server. Under **Admin**, a click on the number of renders an account has left sets a new number for that account.

## Delete your account

**Settings → Delete account**, then type your email address to confirm. It deletes your account, every project, every screenshot, image and render, and every token, at once and for good. There is no undo and no backup to restore from.

One number outlives the account: how many renders it used, kept with a scrambled form of Google's id for you and nothing else. It is there so that deleting an account and signing up again does not hand out a fresh set of renders.

To leave one project only, delete the project. See [The Studio](/docs/studio#projects).

## What is stored about you

Your email address, your name, the fingerprints of your tokens and the number of renders you made, besides the projects you make. The [privacy policy](/privacy) has the full list.

---

# Troubleshooting

Source: https://shots.beffa.xyz/docs/troubleshooting

Start with this. It reports the server, the token, the skill, Xcode, the project and what it is made with, simulators and devices, and every line says what to do next:

```bash
appshots check
```

## Signing in

| The home page says | What to do |
| --- | --- |
| That Google account is not on the list | The server only admits listed people. Ask whoever runs it to add your address |
| Google sign-in was cancelled | You closed Google's page. Try again |
| That sign-in expired | It took longer than ten minutes, or cookies are blocked. Allow cookies for this site and try again |
| Google has not verified that email address | Use a Google account whose address is verified |
| Too many sign-in attempts | Wait fifteen minutes |

## Connecting an agent

**`appshots: command not found`.** The command is in `~/.local/bin`, which is not on your `PATH`. Open a new Terminal window, or add this line to `~/.zshrc`:

```bash
export PATH="$HOME/.local/bin:$PATH"
```

**The agent does not know `/appshots`.** Skills are read when a session starts. Start a new session. If it is still missing, run `appshots check` and look at the `Skill:` line.

**The agent has the skill but no appshots tools.** The MCP connection is missing or was added while the session was running. Start a new session. The skill works without MCP too, through `appshots call`.

**401 Unauthorized.** The token was revoked, or it belongs to another server. Create a new one under **Connect an agent** and run the setup command again. See [Connect an agent](/docs/connect).

**The setup command stops at "Node.js 18 or newer is needed".** Install Node.js 18 or newer from [nodejs.org](https://nodejs.org), then run it again.

**You connected through a connector dialog and it asks for a sign-in page.** appShots has no OAuth. Add it with a header instead. See [Connect an agent](/docs/connect#only-the-mcp-connection).

## Capturing

**"simctl failed" or no simulators.** Xcode is not installed, or it was never opened. Install it from the App Store, open it once, and let it install the iOS platform.

**No booted simulator.** Start your app from Xcode, or run `appshots run`. For an app made with Flutter, React Native, Expo, Capacitor or Tauri, run `appshots boot` and then the toolkit's own run command.

**The build fails in `appshots run`.** Build the app in Xcode first and fix what it reports. If the folder has several projects or schemes, name them with `--project` and `--scheme`.

**The device is not found.** Plug it in, unlock it, and tap **Trust** on it. Capturing from a device needs Xcode 27 or newer. A simulator works with any recent Xcode.

**The status bar shows the wrong time.** Add `--clean`. It works in the simulator only.

**The screenshot shows the launch screen.** The app needed longer to settle. Add `--wait 5`.

**"Could not capture the window" with `--mac`.** macOS only lets a program take pictures of windows with the Screen Recording permission. Switch on your terminal app under **System Settings → Privacy & Security → Screen & System Audio Recording**, then quit and reopen the terminal. Or take the window yourself with Shift-Command-4, Space and a click, and upload the file with `--as mac`. See [Mac apps](/docs/mac#get-the-screens-in).

**`--mac` does not find the app.** The app has to be open with a window on screen. Use its name as the menu bar shows it. The command lists the apps it can see.

**`appshots run` says the project is a Mac app.** `run` is for the iOS Simulator. Start a Mac app your own way and capture its window with `appshots capture --mac`.

**`appshots run` says there is no Xcode workspace or project.** `run` builds Xcode projects only. An app made with another toolkit and a web app are started by their own command, which `appshots check` names. See [Web apps and other toolkits](/docs/web-apps).

**`--web` says nothing answers at the address.** The dev server is not running, or it took another port than usual. Read the address off what it printed when it started.

**`--web` finds no browser.** It draws pages with Google Chrome, Chromium, Microsoft Edge or Brave. Install one, or set `APPSHOTS_BROWSER` to the program of a Chromium browser.

**A web page shows a sign-in or an empty state.** The browser the command starts has a profile of its own and knows none of your sign-ins. Put sample data in with `--init`, or keep a session between captures with `--profile`. See [Reach a screen](/docs/web-apps#reach-a-screen).

**A web app's slides have no status bar.** The capture leaves its place free. Set **Status bar** to overlay in the device settings, or `"device": {"statusBar": "overlay"}` in the theme.

## Slides

**A device is empty.** The slide names a screenshot that is not uploaded. The render lists the missing names. Upload it under exactly that name.

**An iPad slide shows a phone screen.** The project has no iPad screenshot under that name. Upload one with the same name. See [Sizes](/docs/sizes#which-device-a-screenshot-counts-as).

**A Mac screenshot sits in a MacBook, or does not, and it should be the other way.** A captured window is shown as a window, and a display-shaped picture in a MacBook. Set **Frame** in the slide's device settings to **MacBook** or **No frame**. See [Mac apps](/docs/mac#a-window-or-a-macbook).

**App Store Connect says the dimensions are wrong.** Each display only takes its own sizes. Drag the files of the folder named after that display, as listed in the `README.txt` of the ZIP. See [Render and export](/docs/export#upload-to-app-store-connect).

**A screenshot is filed under the wrong device.** Change it with the small menu on its thumbnail under **Assets** in the Studio, or upload it again with `--as iphone`, `--as duo`, `--as ipad` or `--as mac`.

**The headline is tiny.** Text shrinks to fit. Shorten the headline, force a line break, or lower the device with the position control.

**A screenshot was taken for the wrong device kind.** Upload it again, or delete it under **Screenshots** and upload the right one.

**The preview does not show a change the agent made.** The Studio checks for changes every few seconds. Reload the page if it stays behind.

## Rendering and exporting

**"Two renders are already under way for this account".** One render runs and one waits per account. Wait for one to finish, or cancel one with the × beside it under **Renders** in the sidebar.

**"This account has used all of its renders".** Every export uses one render, and the account has none left. Ask the person who runs the server for more. See [Accounts, tokens and limits](/docs/accounts#renders).

**"The render stopped because the server restarted".** The server was updated or restarted while it was drawing. Press **Export ZIP** again. The stopped render was not counted.

**The ZIP did not download when the render was done.** It downloads by itself in the tab that started the render. Press **Download ZIP** under **Renders** in the sidebar. Some browsers block a download that starts in a background tab.

**"Nothing has been rendered yet".** `appshots pull` downloads the last render. Add `--render`.

**The export takes long.** Every language and every size is a full pass. The server also renders one job at a time for everybody, so yours may be waiting.

**App Store Connect refuses a file.** Check that it went onto the display it was made for: the folder name says which. See [Sizes](/docs/sizes).

**The files look different from before.** A project is drawn by the current version of appShots each time. See [Update](/docs/update).

## Limits

**"This account is limited to 5 projects", or the same about screenshots or images.** Delete what you no longer need. The limits are in [Accounts, tokens and limits](/docs/accounts#limits).

## Still stuck

Write to [shots@beffa.xyz](mailto:shots@beffa.xyz) and include what `appshots check` prints. Leave your token out.

---

# Questions and answers

Source: https://shots.beffa.xyz/docs/faq

## The basics

### What is appShots?

A tool that turns raw screenshots of your iPhone, iPad or Mac app into the framed, captioned images the App Store shows, at the exact sizes App Store Connect accepts. You can use it by hand in the browser, or let a coding agent do the whole set. See the [overview](/docs).

### Do I need an agent?

No. The [Studio](/docs/studio) does everything by hand. An agent saves the time: it captures the screens, writes the headlines and builds the set.

### Which agents work?

Claude Code, Codex, OpenCode and Pi have a ready setup. Any other agent works if it can use an MCP server or run shell commands. See [Connect an agent](/docs/connect).

### Do I need a Mac?

For the Studio, any modern browser will do. Capturing screens from a simulator or a device needs a Mac with Xcode, and capturing a Mac app's window needs a Mac. A web app's pages are captured with Chrome, Edge or Brave and no Xcode. Screenshots you have already can be uploaded from anywhere.

### How long does a set take?

With an agent, ten to twenty minutes from "make my screenshots" to the ZIP. Most of that is building the app and opening its screens.

### Is it made by Apple?

No. appShots is not affiliated with Apple. App Store and iPhone are trademarks of Apple Inc.

## What it makes

### Does it use AI to generate the images?

No. The server draws every slide from your screenshots, text and settings, the same way each time. There is no image model, and the server calls no AI service. The agent you connect is what writes the copy and picks the layouts.

### Can it make Google Play screenshots?

Not at Google Play's sizes. It exports App Store sizes for iPhone, iPhone Duo, iPad and Mac. See [Sizes](/docs/sizes).

### Do I have to design every iPhone size?

No. You design the set once for the iPhone, and once each for the iPhone Duo and the iPad if your app runs on them. The export holds every size App Store Connect lists. See [Sizes](/docs/sizes).

### Does it work for Mac apps?

Yes. The Mac is a device like the iPhone and the iPad. Capture the app's window with `appshots capture --mac`, or upload a screenshot of it, and the set is exported in landscape at the Mac App Store's size. A window is shown as a window, an app in full screen inside a MacBook. See [Mac apps](/docs/mac).

### Does it work with Flutter, React Native or a web app?

Yes. An app is captured where it runs, whatever it is built with. A Flutter, React Native, Expo or Capacitor app runs in the simulator, started by its own command. A Tauri or Electron app is captured as a window on the Mac. A web app is captured page by page with `appshots capture --web`, at the size of an iPhone, an iPad or a Mac. See [Web apps and other toolkits](/docs/web-apps).

### Can it make a banner for my README?

Yes. A project can have a README banner, a social preview for the repository, a link preview and a 16:9 picture for a post, made from the same screenshots and theme. See [Banners](/docs/banners).

### Can I get the device alone, for my website?

Yes. The **Mockups** page of a project shows every screenshot in its device with nothing behind it, to download as a transparent PNG. An agent and the `appshots mockups` command do the same. See [Mockups](/docs/mockups).

### Landscape screenshots, or app preview videos?

iPhone and iPad sets are portrait, and a Mac set is landscape. There are no landscape iPhone or iPad sets and no videos.

### Can I use my own fonts?

Not yet. There are 41 built in, listed on [Slides](/docs/slides#fonts). They are open-source fonts that are free to use in commercial images.

### Can I add my own logo or artwork?

Yes. See [Logos and artwork](/docs/images).

### Are the device frames allowed on the App Store?

The frames are drawn by appShots and are not Apple's product photos. Framed screenshots are common on the store. Apple decides each case. See [What App Review accepts](/docs/app-review).

### Who owns the images?

You do. They are made from your screenshots and your words.

## Your data

### Who can see my screenshots?

Your account, and the person who runs the server, who has access to its disk. No other account can. See the [privacy policy](/privacy).

### Does my agent send my code to appShots?

No. The agent reads your code on your Mac. What reaches the server is the screenshots it uploads and the slides it writes. What your agent sends to its own maker is between you and them.

### How do I delete everything?

**Settings → Delete account** in the Studio. See [Accounts, tokens and limits](/docs/accounts#delete-your-account).

## Working with it

### Can I change what the agent made?

Yes. Everything is editable in the Studio, and the agent can keep working on it afterwards. Both edit the same project.

### How many sets can I export?

An account gets 10 renders, and every export uses one. Working in the Studio and looking at the previews uses none. When they are used up, the person who runs the server can give you more. See [Render and export](/docs/export#how-many-renders).

### Do I have to wait while it renders?

No. **Export ZIP** renders in the background. Keep working or switch to another tab, and the ZIP downloads when it is done.

### Can two people work on one project?

Not yet. A project belongs to one account.

### How do I get the newest version?

The Studio and the server's tools update by themselves. On your Mac, run `appshots update`. See [Update](/docs/update).

### Something does not work.

See [Troubleshooting](/docs/troubleshooting), and run `appshots check`.

---

# Privacy policy

Source: https://shots.beffa.xyz/privacy

Last updated: 11 October 2026

This page says what this appShots server stores about you and what happens to it. It applies to https://shots.beffa.xyz.

## In short

- You sign in with Google. The server stores your email address and your name.
- Your projects, screenshots and renders are stored so you can work on them. No other account can see them.
- Nothing is sold, and nothing is used for advertising.
- The server calls no AI service.
- Deleting your account deletes all of it at once. Only a count of the renders it used stays, with no name or address beside it.

## Who is responsible

This server is run by beffa.xyz.
For anything about your data, write to [shots@beffa.xyz](mailto:shots@beffa.xyz).

## What is stored

**Your account.** From Google: your email address, your name and Google's id for your account. Also the day the account was created and the number of renders you made.

**Your API tokens.** For each token: a fingerprint (a SHA-256 hash), its last four characters, the day it was created and the day it was last used. The token itself is not stored.

**Your work.** The projects you make: their names, the texts and settings of every slide, the screenshots and images you upload, and the rendered files.

**The list of people.** If the server only admits listed people, the email addresses on that list.

That is all the server keeps. It writes no log of the pages you open or the requests you make.

## What is kept in your browser

| Name | Kind | What it is for | How long |
| --- | --- | --- | --- |
| `appshots_session` | Cookie | Keeps you signed in. Scripts on the page cannot read it | 30 days after your last visit |
| `appshots_oauth` | Cookie | Protects the sign-in while you are on Google's page | 10 minutes |
| `appshots.theme` | Local storage | Your choice of light or dark | Until you clear it |
| `appshots.guides` | Local storage | Whether the safe area guides are shown | Until you clear it |

These are needed for the site to work. None of them follows you to other sites.

## Signing in with Google

When you sign in, your browser goes to Google and comes back. The server asks Google for your email address, your name and your account's id, and for nothing else. It cannot read your mail, files or contacts. It never receives your Google password. Google learns that you signed in to this site. [Google's privacy policy](https://policies.google.com/privacy) covers their side.

## Analytics

This server uses PostHog (`stats.beffa.xyz`) to see how the site is used: which pages are opened, which buttons are clicked, and what kind of browser and screen is used. PostHog keeps an anonymous id in a cookie and in your browser's storage for this, and sees your IP address when your browser contacts it.

- You are **not identified**. Your email address and name are never sent to PostHog.
- On the public pages, PostHog may record the session: how the page was scrolled and clicked.
- **In the Studio, session recording is switched off.** Your projects and screenshots are not recorded.
- A browser's tracking protection or a content blocker stops all of it, and the site works the same.

[PostHog's privacy policy](https://posthog.com/privacy) covers their side.

## Security

- Connections are encrypted with HTTPS.
- Sign-in attempts are counted per IP address for fifteen minutes, in memory only, to stop guessing. Nothing of it is written to disk.
- Each account's files are kept apart, and every request is checked against the account that sent it.
- The company hosting the server may keep technical connection logs of its own.

## Who receives your data

| Who | What | Why |
| --- | --- | --- |
| Google | That you signed in to this site | Sign-in |
| PostHog | Anonymous usage data and your IP address | Analytics |
| The hosting provider of this server | Everything stored, as files on its disks | Running the server |

Nobody else. Your data is not sold, rented or shared for advertising, and it is not used to train AI models.

**Your agent is separate.** If you connect a coding agent, what it reads on your Mac and sends to its own maker is governed by that maker's terms, not by this page. appShots only receives what the agent uploads here.

## How long it is kept

Until you delete it.

- Deleting a screenshot, an image or a project removes it at once.
- Deleting your account removes the account, every project, every file and every token at once.
- One thing stays after that: the number of renders the account used, next to a SHA-256 hash of Google's id for it. It holds no name and no address, and it only serves to give you the same count back if you sign up again.
- If the person running the server keeps backups of its disk, deleted data can remain in those until they are overwritten.

## Your rights

You can, at any time:

- **See and take out your data.** Everything is in the Studio. **Export ZIP** downloads the renders, and the [HTTP API](/docs/api) returns each project as JSON.
- **Correct it.** Projects are yours to edit. Your name comes from Google on sign-in.
- **Delete it.** See [Accounts, tokens and limits](/docs/accounts#delete-your-account).
- **Ask.** Write to [shots@beffa.xyz](mailto:shots@beffa.xyz) for a copy of what is stored, or to object to how it is used.

If you live in the European Union, the United Kingdom or another place with a data protection law, you also have the right to complain to your data protection authority.

## Children

appShots is a tool for people who publish apps. It is not meant for children, and accounts need a Google account.

## Changes

When this page changes, the date at the top changes with it.

---

# Terms of use

Source: https://shots.beffa.xyz/terms

Last updated: 11 October 2026

These are the terms for using this appShots server at https://shots.beffa.xyz. By signing in you agree to them.

The service is provided by beffa.xyz.

## What the service is

appShots makes App Store screenshots from the screenshots, images and text you give it. See the [overview](/docs).

## Your account

- You sign in with a Google account that is yours.
- Keep your API tokens secret. What is done with a token of yours counts as done by you. Revoke a token you think has leaked.
- The limits per account are listed under [Accounts, tokens and limits](/docs/accounts#limits). They can change.

## Your content

- **What you upload and what you make stays yours.** The service claims no rights to your screenshots, images, texts or the rendered files.
- You allow the server to store and process them for one purpose: making and showing you your screenshot sets.
- Upload only what you have the right to use. That goes for screenshots, logos, artwork, and other people's names and words.
- You are responsible for what your screenshots claim. Ratings, download counts, awards and reviews must be real.

## What is not allowed

- Uploading anything illegal, or anything that infringes somebody else's rights.
- Trying to reach other accounts' data, or to get around the limits.
- Overloading the server, or using it to attack anything.
- Reselling access to this server without permission.

An account that does these can be closed.

## Fonts, icons and logos

- The fonts are open-source fonts under their own licences, which allow use in images.
- The brand icons are trademarks of their owners. They are included so you can show which services your app works with. Whether you may use a logo is between you and its owner. See [What App Review accepts](/docs/app-review#other-companies-names-and-logos).
- appShots is not affiliated with Apple. App Store, iPhone and iPad are trademarks of Apple Inc.

## No promises

- The service is provided as it is. It can change, be unavailable, or end.
- **Nothing here promises that Apple accepts your screenshots.** App Review decides.
- Keep your own copies of what matters. Download your renders, and keep your original screenshots.
- As far as the law allows, the provider is not liable for lost data, lost profit or a rejected submission that follow from using the service.

Nothing in these terms limits rights the law gives you that cannot be given up.

## Ending

You can stop at any time by deleting your account, which deletes your data. See the [privacy policy](/privacy). The provider can close accounts that break these terms, and can end the service. If the service ends, you get reasonable notice to download your work where that is possible.

## Changes

When these terms change, the date at the top changes with it. If you keep using the service after a change, the new terms apply.

## Contact

[shots@beffa.xyz](mailto:shots@beffa.xyz)
