App manifest
The manifest is your listing and your app's settings in one JSON object: the fields the listing form in the developer portal saves to your draft.
The form saves as you type, and only what changed: every field is optional on a save. Saving never submits.
Fields
Types and limits below are the ones the API enforces when you save. “Required to submit” rules are listed after the table.
| Field | Type and limits | What it is |
|---|---|---|
name | string1 to 80 characters | The app’s name on the store. |
categorySlug | stringup to 48 characters | One of GET /api/public/store/v1/categories (bookings, food, selling, learning, events, listings, business, websites). |
tagline | string | nullup to 60 characters | One line under the name. Required to submit. |
description | string | nullup to 5000 characters | What the app does, in plain words. At least 40 characters to submit. |
dataNote | string | nullup to 2000 characters | What the app stores about the owner’s customers and why. Shown under “Your data”. At least 20 characters to submit. |
supportEmail | string | nullup to 254 characters, email | Where buyers reach you. Falls back to your developer profile’s support email. |
languages | string[] | nullup to 30 items | Language codes the app works in, e.g. en, es. |
plans | "website" | "app" | "business"[]up to 3 items | EzyStudio plans the app works on. At least one to submit. |
pricingModel | "free" | "one_time" | "monthly" | "included" | free, one_time or monthly. included is for EzyStudio’s own apps only (422 otherwise). |
priceCents | integer | null0 to 99900 | Price in cents for one_time and monthly: from the minimum paid app price (priceMinimum on the app, set by EzyStudio; $3 by default) to $999 (99900). A lower price is refused with E_PRICE_BELOW_MINIMUM (meta.minimumCents). An app already live below the minimum may keep or raise its price, never lower it. Cleared for free. |
currency | "USD" | USD only for now. |
customization | object | What a buyer’s one-sentence customization may change. Defaults: colors, text and fields on, layout off. |
customization. | boolean | Colors and fonts. |
customization. | boolean | Names, labels and copy. |
customization. | boolean | Form fields. |
customization. | boolean | Layout and order of sections. |
customization. | stringup to 500 characters | An example sentence for buyers. |
included | any[]up to 50 items | Listing bullets: what the app includes (strings or objects, up to 50). |
features | any[]up to 50 items | Feature cards on the listing, e.g. {icon, group, title, description} (up to 50). |
setupSteps | any[]up to 50 items | Steps shown while the app installs. Strings or {title} / {label} objects; the first 6 are used. |
madeFor | any[]up to 50 items | Kinds of businesses it is for (up to 50). |
trySaying | any[]up to 50 items | Example customization sentences (up to 50). |
changelog | string | nullup to 2000 characters | What’s new: release notes customers see on the listing’s changelog. Up to 2,000 characters. Required for every version after the first, optional on the first. |
customerImpact | string | nullup to 2000 characters | What customers might lose: removed features, changed or deleted data, breaking changes. Up to 2,000 characters. Required after the first version unless customerImpactNone is true. |
customerImpactNone | boolean | True when nothing is removed or changed in a way customers lose (instead of customerImpact). |
aiUpdateNotes | string | nullup to 8000 characters | Update instructions for the AI that applies the update to each customer’s customised copy: what changed since the previous version, data migrations, moved files, how to carry customisations over. Up to 8,000 characters. Required after the first version. |
version | stringpattern ^\d{1,4}\.\d{1,4}(\.\d{1,4})?$ | The version number you propose: major.minor or major.minor.patch. Unique per app, and higher than the live version. A new draft starts at the next minor (1.0 → 1.1). EzyStudio confirms the final number when it publishes the version. |
repo | object | null | Where the app’s code comes from: a GitHub repository and branch of your developer team’s GitHub connection (connect it on the submit form). Saving it checks the branch’s latest commit against the bundle rules (422 E_BUNDLE_INVALID, meta.field: repo). At submit we freeze the branch’s latest commit: its files are what reviewers test and customers install. Required to submit. null clears it. |
repo.Required | integer0 to 9007199254740991 | GitHub’s installation id, from the GitHub connection’s installations. |
repo.Required | integer0 to 9007199254740991 | GitHub’s repository id (one the installation covers). |
repo.Required | string1 to 255 characters | The branch to take the code from. |
repo. | boolean | Check the branch’s latest commit again even when the pick is unchanged. |
media | object[]up to 10 items | Replaces the whole list. Up to 1 icon, 1 cover and 8 screenshots; order = position within each kind. |
media[].Required | "icon" | "cover" | "screenshot" | icon, cover or screenshot. |
media[]. | stringup to 512 characters | The key from the presign answer (preferred). |
media[]. | stringup to 1024 characters, uri | Only a URL this API returned for one of this app’s files. New outside URLs are refused. |
media[]. | stringup to 250 characters | Required on every image to submit. |
media[]. | stringup to 120 characters | Optional caption. |
media[]. | integer1 to 20000 | Pixels. |
media[]. | integer1 to 20000 | Pixels. |
What submit needs
Saving accepts an unfinished draft. Submitting checks the draft and answers 422 E_DRAFT_INCOMPLETE with every problem in meta.problems, each with its field (first column; the manifest field it checks is in brackets when the name differs), the form step to fix it in and the message:
| Field | Rule to submit | Message |
|---|---|---|
name | Not empty | Give your app a name |
category (categorySlug) | A category is set | Pick a category |
tagline | Not empty, 60 characters at most | Add a short tagline |
description | At least 40 characters | Describe what the app does in a sentence or two |
dataNote | At least 20 characters | Say what your app stores and why |
plans | At least one plan | Pick at least one plan |
supportEmail | Set here or on your developer profile | Add a support email so buyers can reach you |
screenshots (media) | 1 to 8 screenshots, alt text on every image | Add at least one screenshot · Add alt text to every screenshot |
repo | A GitHub repository and branch are picked under App code. Saving them checks the branch's latest commit against the bundle rules (E_BUNDLE_INVALID); submit freezes the latest commit. | Connect GitHub and pick the repository and branch of your app's code |
changelog | After the first version: not empty (2,000 characters at most) | Say what's new in this version |
customerImpact | After the first version: not empty (2,000 characters at most), or customerImpactNone is true | Say what customers might lose, or tick that nothing is removed |
aiUpdateNotes | After the first version: not empty (8,000 characters at most) | Write the update instructions for the AI |
version | After the first version: higher than the live version | Use a version number higher than 1.0 |
price (priceCents) | For one_time and monthly: at least the minimum paid price EzyStudio sets ($3, 300, by default). Saving a lower price is refused (E_PRICE_BELOW_MINIMUM), and so is more than $999 (99900). | Enter a price of $3 or more |
Pricing fields
free: no price and no checkout. It installs on the plans you list inplans.one_time: the buyer payspriceCentsonce.monthly: the buyer payspriceCentsevery month until they cancel.included: part of an EzyStudio plan. Only EzyStudio's own apps can use it; anything else answers422 E_INVALID_PRICING.
plans lists the EzyStudio plans the app works on (website, app, business). It is shown on the listing and an install on any other plan is refused. Read more in Pricing and payouts.
Customization
Owners customize your app with one sentence. customization says which parts that sentence may touch: colors, text, fields, layout. Anything you switch off is skipped, and payments and stored customer data are always left alone. New apps start with colors, text and fields on and layout off. Try a sentence on the Customization step of the listing form before you submit: reviewers do the same.
How it is applied: EzyStudio installs your files as they are, then hands the sentence to the owner's own AI in their editor, with the parts it may change. The AI edits your settings file first (named in your bundle's ezystudio-app.json), so keep colors, text, fields and layout options there. An owner can change the sentence later; the same rules apply. See The bundle.
Example
{
"name": "Clinic Intake Forms",
"categorySlug": "bookings",
"tagline": "Friendly check-in forms patients fill in before their visit.",
"description": "Send new patients a link to a friendly check-in form before their visit. They add their details and consent on their phone.",
"dataNote": "Patient answers are stored in the clinic's own EzyStudio database, visible only to logged-in staff.",
"supportEmail": "help@kilnlabs.test",
"languages": ["en"],
"plans": ["app", "business"],
"pricingModel": "monthly",
"priceCents": 1500,
"currency": "USD",
"customization": {"colors": true, "text": true, "fields": true, "layout": false,
"sample": "We're a family dental practice in Leeds, open Monday to Saturday."},
"included": ["Check-in form", "Consent capture", "Staff inbox"],
"features": [{"icon": "clipboard-list", "group": "Forms", "title": "Check-in form", "description": "Details, history and consent on any phone."}],
"setupSteps": ["Install", "Describe your clinic", "Publish"],
"madeFor": ["Clinics", "Dentists", "Physiotherapists"],
"trySaying": ["Add a field for insurance number."],
"version": "1.1",
"changelog": "Patients can now sign the consent form on their phone.",
"customerImpact": "The printable PDF form is gone. Forms already saved stay as they are.",
"customerImpactNone": false,
"aiUpdateNotes": "The consent step moved from Booking.astro into src/apps/clinic-intake/Consent.astro. Migration 20261029120000 adds app_clinic_intake_consents. Keep each clinic's colours and extra fields.",
"repo": {"installationId": 51234567, "repositoryId": 81234567, "branch": "main"},
"media": [
{"kind": "icon", "key": "apps/42/icon/01jb2x4c1d.png", "altText": "Clinic Intake Forms icon"},
{"kind": "screenshot", "key": "apps/42/screenshot/01jb2x5m3a.png", "altText": "The check-in form on a phone", "width": 1290, "height": 2796}
]
}media[].key are the keys of images uploaded to EzyStudio, never links to images hosted elsewhere. The listing form fills them in when you add your images. Your code is not uploaded: repo names a repository and branch of your GitHub connection, and submit freezes that branch's latest commit. The repository's layout (ezystudio-app.json, files/, where files may go) is in The bundle; saving repo checks the latest commit, and so does submit; both answer 422 E_BUNDLE_INVALID with the reason.
Sample data for your review test site is not a listing field: name it in the bundle's ezystudio-app.json as sampleData. See Sample data for review.