> ## Documentation Index
> Fetch the complete documentation index at: https://developers.usezend.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Manifest

> Every field in zapp.json and the rules Zend checks when you deploy.

The manifest tells Zend where your app lives, what it may do, and how its interactions behave. Keep it in your repo as `zapp.json` and deploy it with [`zend apps deploy`](https://developers.usezend.app/cli/apps#deploy).

Each upload creates a new version and makes it active. Zend checks the whole file and returns every problem at once as `422 INVALID_MANIFEST` with an `errors[]` list. Unknown fields are rejected at every level, so a typo fails loudly instead of being ignored.

## Example

```json zapp.json theme={null}
{
  "app": "accident",
  "name": "Accident",
  "version": "1.0.0",
  "origins": ["https://accident.example.com"],
  "entry": "/",
  "visibility": "private",
  "permissions": ["post_card", "pay", "request_payment", "create_link"],
  "chatActions": [
    { "id": "challenge", "label": "Challenge", "sheet": "/play", "interaction": "challenge" }
  ],
  "cardTypes": ["result", "invite"],
  "interactionTypes": {
    "challenge": {
      "states": ["sent", "accepted", "resolved", "expired"],
      "initial": "sent",
      "transitions": {
        "sent": ["accepted", "expired"],
        "accepted": [{ "to": "resolved", "serverOnly": true }, "expired"]
      },
      "terminal": ["resolved", "expired"]
    }
  },
  "custody": "contract",
  "webhook": "https://accident.example.com/zend/webhook"
}
```

## Fields

**app** `string` *required*

Your app slug. 2–31 characters of `a-z`, `0-9` and `-`, starting with a letter or digit. Must match the slug you created.

**name** `string` *required*

Shown to users. 1–24 characters, plain language (see [Plain-language check](#plain-language-check)).

**version** `string` *required*

Your own version label. Zend numbers uploads separately (`v1`, `v2`, …).

**origins** `string[]` *required*

1–5 origins in the form `https://host[:port]`. No path, query or fragment, and not on `usezend.app`. Pages outside these origins open in the browser instead of inside Zend.

**entry** `string` (default: /)

Path Zend opens by default. Must start with `/`.

**visibility** `"private" | "public"` (default: private)

`public` sends the app to review. See [Public apps](https://developers.usezend.app/ship/public-apps.md).

**permissions** `string[]` (default: \[])

What your app may ask for. See [Permissions](#permissions).

**chatActions** `object[]` (default: \[])

Entries in the chat **Apps** tray. See [chatActions](#chatactions).

**cardTypes** `string[]` (default: \[])

Card templates you post: `result`, `invite`, `status`. Needs `post_card`.

**interactionTypes** `object` (default: \{})

Map of interaction type name to its state machine. See [interactionTypes](#interactiontypes).

**custody** `"contract" | "creator"` (default: contract)

Where group, event and prize money sits before payout. Public apps must use `contract`. See [Custody](https://developers.usezend.app/concepts.md#custody).

**webhook** `string`

`https` URL that receives [webhooks](https://developers.usezend.app/reference/webhooks.md).

**limits** `object`

Only `cardsPerUserPerDay` is supported. It can lower your tier's cap, not raise it.

**description** `string`

One line for Discover, up to 140 characters, plain language. Required for public apps.

**category** `string`

Discover section: `games`, `friends`, `events`, `community` or `tools`.

**developer** `object`

`{ name, support, privacy }`. Required for public apps. `support` is an email or `https` URL; `privacy` is an `https` URL.

**results** `"none" | "app" | "checked"` (default: none)

How results are decided, shown on your listing. Must be `app` or `checked` if you use `prize`. See [Public apps](https://developers.usezend.app/ship/public-apps.md#results).

**resolverKey** `string`

32-byte hex Ed25519 public key that signs prize results. Required with the `prize` permission. Generate one with [`zend apps resolver-key`](https://developers.usezend.app/cli/apps#resolver-key).

**adapters** `string[]`

Reserved. Must be empty.

## Permissions

| Permission | Unlocks | User consent |
| - | - | - |
| `post_card` | [`postCard`](https://developers.usezend.app/guides/cards) | "Post to your feed and chats" toggle |
| `pay` | [`pay`](https://developers.usezend.app/money/pay) | Each payment |
| `request_payment` | [`request`](https://developers.usezend.app/money/requests-and-links) | Each request |
| `create_link` | [`createLink`](https://developers.usezend.app/money/requests-and-links) | Each link |
| `prize` | [`prize.*`](https://developers.usezend.app/money/prizes) | Each prize |
| `attest` | [`attest`](https://developers.usezend.app/fairness/attestations) | None |
| `email` | `email` from [`verifySession`](https://developers.usezend.app/guides/identity) | "See your email" toggle |
| `wallet_address` | `address` from `verifySession` | "Can send money straight to you" toggle |
| `contacts` | Reserved | "See people you choose to share" toggle |
| `update_bubble` | Reserved | None |

Consent toggles start off. Users can change them later in **Settings → Connected apps**.

## chatActions

```json theme={null}
{ "id": "challenge", "label": "Challenge", "sheet": "/play", "interaction": "challenge" }
```

| Field | Rules |
| - | - |
| `id` | Unique within the manifest |
| `label` | 1–24 characters, plain language |
| `sheet` | Path on your first origin, starting with `/` |
| `interaction` | Optional. A key of `interactionTypes` |

When `interaction` is set, Zend creates that interaction with the other person in the chat and opens `sheet?interaction=<id>`.

## interactionTypes

```json theme={null}
"challenge": {
  "states": ["sent", "accepted", "resolved", "expired"],
  "initial": "sent",
  "transitions": {
    "sent": ["accepted", "expired"],
    "accepted": [{ "to": "resolved", "serverOnly": true }, "expired"]
  },
  "terminal": ["resolved", "expired"]
}
```

* 1–12 unique states. `initial` and every `terminal` state must be in `states`.
* At least one terminal state. Terminal states can't have transitions.
* Transitions reference known states only, and each move can be listed once.
* A transition is either a state name, which any participant may make, or `{ "to": "<state>", "serverOnly": true }`, which only your server may make with your app key. A page that tries it gets `TRANSITION_SERVER_ONLY`.
* If you declare a state named `expired`, Zend moves unfinished interactions there at their deadline.

> [!TIP]
>
> Use `serverOnly` for any move a player could fake by editing the page, such as declaring a winner. `zend apps check` lists every server-only move and catches a misspelt `serverOnly`, which would otherwise leave the move open to every player.

## Plain-language check

`name`, chat action labels, `description`, `developer.name` and card text can't contain these words: sui, blockchain, chain, crypto, token, usdc, stablecoin, gas, wallet, escrow, contract, zklogin, seed. Your users shouldn't need to know how money moves. See the [copy guide](https://developers.usezend.app/ship/copy-guide.md).

## Check before you deploy

```bash theme={null}
zend apps check zapp.json
```

`check` runs the interaction-rule checks locally without uploading. `deploy` runs the same checks first. The server validates everything else on upload.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.