Salla Publication Consistency
Scope: PUBLIC apps (App Store listing). Private apps don't use this stepwise flow and
have no MCP publish action — the partner sends their publish request from the app-details
page https://portal.salla.partners/apps/{app_id} (see salla-app-builder).
Before you can publish — prerequisites
Check these first; they gate the whole flow regardless of the sections:
All three are readable from app details — app_publish action=get (or salla_apps action=get)
returns can_publish, requires_citc, and can_have_free_plan. Check them before filling:
- The app must be publishable.
can_publish must be true. If false, the app cannot be
submitted.
- The partner's account must be verified. Before publishing, the partner verifies their
account at https://portal.salla.partners/account. An unverified account blocks submission
(validate/submit returns an
id_verification error).
- CITC certification (SMS communication apps). When
requires_citc is true, the partner
must upload a CITC certification on the account verification form before publishing (Saudi
regulatory requirement) → salla-communication-app.
Prepare a Salla app for review via app_publish (base /app/{id}/publication): a
readiness-driven loop, not one bulk call. Fill sections one at a time; the server decides
when the draft is ready.
open → get (read the current draft) → (set <section> → readiness)* → validate → partner reviews the /publish link → send_publish_request
app_publish action=validate validates every section for completeness/correctness and
saves a DRAFT, returning a valid publication — it does not submit. Sending the app
to Salla review is HARD-GATED: after a clean validate, surface the partner's real
/publish link (id substituted) and ask them to REVIEW the draft. Only then does it go to
review — either the partner submits one-click in the Portal, or, after they
explicitly confirm, the agent calls app_publish action=send_publish_request with
confirm: true. Never send the request before a clean validate and the partner reviewing
the link and confirming.
Drive off the readiness checklist returned by open and every set: it marks each section
complete or, for incomplete ones, lists the exact missing fields. Fix the one section it
points at, re-check, repeat. validate runs the same server-side gate and returns 422 +
missing sections if anything is incomplete, and saves the draft when every section is
complete.
The loop (app_publish, base /app/{id}/publication)
| action |
verb |
body |
does |
open |
POST |
— |
Create the draft + return the per-section readiness checklist; opening also makes app_page_builder available for listing content. |
get |
GET |
— |
Read-only: the FULL current draft (publication_last_save — every saved value) + scopes + webhooks. RESUME/REVIEW entry — readiness shows what's MISSING, get shows what's THERE. |
readiness |
GET |
— |
Re-fetch the checklist: which sections are complete + the exact missing fields. Pure read. |
set |
PUT |
{ section, ...data } |
Write ONE section; only the fields you pass are touched; returns updated readiness. |
validate |
PUT |
— |
Validate all sections + save the DRAFT; returns a valid publication. Incomplete → 422 + missing sections. Does not submit. |
send_publish_request |
PUT |
confirm:true |
Send the app to Salla review — HARD-GATED: only after validate + the partner reviewed the /publish link + confirmed. Without confirm:true it does NOT submit (returns the link). |
Listing content (name, description, logo, screenshots, benefits) is authored with
app_page_builder → salla-app-ui-builder.
Read the current draft first (resume / review)
Before filling or fixing anything — especially when resuming a draft or asked to review one —
read it with app_publish action=get. It returns publication_last_save (every saved section
value) plus the app's scopes and webhooks. readiness tells you what's missing; get
tells you what's already there — never re-ask the partner for a value the draft already holds,
and never blind-overwrite a section you haven't read.
Validation is step-by-step
Three layers, in order — don't defer everything to one bulk check at the end:
set validates FORMAT per section (server PublicationSectionRequest: e.g.
short_description 50–200, video_url/URLs well-formed, plan numeric rules). A set that
returns 422 is THIS section's validation error — the tool surfaces the field messages; fix
them and set again before moving on.
readiness reports COMPLETENESS — which sections are complete and the exact missing
(blank) fields. Completeness ≠ correctness: a field can be present-but-invalid (caught by
set) or present-but-blank (caught by readiness).
validate is the final CROSS-FIELD + requiredness gate over the whole draft — it saves
the DRAFT when clean, or returns 422 + the still-missing sections.
The loop: set <section> → if 422, fix this section → readiness → next. Catch each section's
errors as you write it, not in one end-of-flow validate.
Step map — this skill is the master router
Each publication step routes to the skill that owns its modelling, and each has a reference
(data retrieval → submission schema → how to submit):
| Step (publication section) |
Owner skill |
Reference |
| Basic Information |
this skill |
step-basic-information.md |
App Config (scopes/webhooks — not a set section) |
salla-app-auth + salla-webhooks |
step-app-config.md |
| Features (banner / embedded image; screenshots/benefits) |
salla-app-ui-builder |
step-features.md |
| Pricing |
salla-app-billing |
step-pricing.md |
| Contact Information |
this skill |
step-contact.md |
| Service Trial / Review |
this skill + salla-app-billing |
step-service-trial.md |
app_page_builder is the App Store LISTING/PROFILE page builder — the public app profile
(rating, plans, install button). It is publication-coupled: it pulls images/text/plans from the
draft, can be edited against a draft publication, and its images and content override the
publication's. It is NOT the post-install embedded dashboard UI (→ salla-embedded-app /
salla-embedded-ui).
The 5 sections at a glance
Pass section + only the fields you're writing (localized fields take { ar, en }; image fields
an integer id from salla_upload; multi-value fields an array). Per-field schema, server rules,
and a worked set example are in each step's reference (Step map above) — read it before writing.
| Section |
Sets (summary) |
Reference |
basic_information |
short_description, main_category_id (App Theme/Impact), categories, video_url (required), demo_url, search_terms, supported_countries |
step-basic-information.md |
features |
banner, embedded_image (screenshots/benefits → app_page_builder) |
step-features.md |
pricing |
plan_type + that type's plan/addon/once fields |
step-pricing.md → salla-app-billing |
contact_information |
contact_method, notification/submission/support contacts, website_url, policy_url, faq_url |
step-contact.md |
service_trial |
service_link, trial_username, trial_password, trial_description, update_note (updates only) |
step-service-trial.md |
Route elsewhere — not set via app_publish
These live outside the publication sections. Finalize each in its own tool, then re-check
readiness:
| What |
Where |
Listing content: name, description, logo, screenshots, benefits |
app_page_builder → salla-app-ui-builder |
| OAuth scopes (request the minimum needed) |
salla_scopes → salla-app-auth |
| Webhook url / signing secret / headers |
salla_apps action=connect → salla-webhooks |
Store-event subscriptions (order.*, product.*, …) |
salla_events action=subscribe → salla-webhooks |
| Merchant settings FORM |
salla_settings → salla-app-settings |
Security review checks belong to the owning skills: minimum OAuth scopes →
salla-app-auth; webhook signature verification + authenticated interfaces →
salla-webhooks; secret handling → keep credentials test-only, redacted, never plaintext.
The draft is a snapshot taken at validate, not a live mirror. Settings snapshot when the
draft is saved and are matched against the live state, so finalize the external pieces
before you validate and do settings last:
- Finalize the settings form (
salla_settings) before validate — whatever is live when
the draft is saved is what the partner submits.
- Communication apps must declare supported features
(
salla_settings action=set_features) before validate, or the gate blocks.
- After changing any external piece (scopes, webhook, events, settings, builder content),
re-run
readiness (re-open if needed) and validate again before handing off.
First-time publish — guided onboarding, not a blind fill
The publication sections carry the partner's business decisions — listing copy,
categories, pricing/plans/trial, contact, supported countries, screenshots/benefits. For a
first-time publish, walk the partner through the sections one at a time; fill each
from their answers, never invent or auto-fill. At each section:
- Ask the right questions for that section, e.g.:
basic_information → value prop / short description, main category + sub-categories,
supported countries, a demo/video URL? (video_url is required — if they have none, point
them to the educational-video guide: https://salla.dev/tutorial/educational-video-guidelines-apps-ar/)
pricing → free or paid? one-time, subscription plans, or addons? a trial?
contact_information → support email/phone, policy + FAQ URLs?
- App Information (
app_page_builder) → name, description, logo, ≥3 screenshots, benefits?
- Suggest valid options grounded in Salla so the partner picks from real choices, not
guesses: real categories from
salla_reference action=categories; the pricing models
Salla billing supports (free / one-time / subscription plans + addons + trial →
salla-app-billing); supported-country options from salla_reference action=countries.
- Fill from the answers, then run
app_publish action=readiness to see what's still
missing, and move to the next section.
Suggest the publication-window monetization features. First publish is the moment to set
these — surface them as grounded opt-in options (never auto-add), each routing to
salla-app-billing / salla-addon-purchase:
- Addons — extra in-app purchases on top of any plan.
- Trial — a free
plan_trial (days) and/or the reviewer service trial.
- Promotions — time-boxed plan discounts (
promotions[]).
- Comparison matrix —
plan_features[] to compare plans side by side.
- Recommended / highlighted plan —
recommended, is_compare_included.
- Strikethrough pricing —
one_time_old_price (once) or a plan's old_price.
- Adjustable quantities —
plan_additional_features (once) / per-plan additional_features.
- Churn-prevention —
unsubscribe_reward, unsubscribe_email_reward.
Note which are publication-window only; per-merchant/tailored plans come later via App
details → Custom Plans. Any paid choice triggers the billing-cycle gate (below).
This composes with the image-asset rule below (ask for images; on skip → clearly-marked
placeholders + tell the partner to replace them).
Gate: "Each section filled from the partner's answers — listing copy, categories,
pricing, contact — not auto-invented?"
Listing images — ask first, placeholder only with a heads-up
The listing needs real images: logo + ≥3 screenshots (App Information),
banner + embedded_image (App Features). These are owned by
salla-app-ui-builder — read it for the fields, dimensions, and the generate-then-upload
recipe. The discipline here:
- If you don't have the images, ASK the user to provide all the required ones. Don't
invent a real-looking image and don't silently skip them — this is verify-don't-invent.
- If the user skips or declines, proceed with clearly-marked default placeholders so the
draft still validates — then explicitly tell the partner to replace the placeholders in
the Portal before they do the one-click submit. Never present a placeholder as final.
Gate: "Every listing image is a real partner asset — or a placeholder the partner has
been explicitly told to replace before submitting?"
Pre-publication sequence (in order)
- Open the draft (
action=open); read the initial readiness checklist.
set each incomplete section's data, re-checking readiness after each — one section
at a time, driven off the returned missing list.
- Author builder content (screenshots, benefits, listing name/description/logo) via
app_page_builder → salla-app-ui-builder so features completes — applying the
image-asset rule above for any missing image.
- Finalize the settings form (
salla_settings); for communication apps run
salla_settings action=set_features.
- Re-run
readiness — confirm all 5 sections read complete.
- Validate + save the draft (
action=validate). On 422, set the missing sections
and validate again until it returns a valid publication.
6b. Billing-cycle gate — paid pricing only, BEFORE submit. If the draft's pricing has a
recurring plan or any addon, the app must handle the subscription lifecycle before it can go to
review. The lifecycle rides on app events (app.subscription.* / app.trial.*), which the
app is subscribed to by default — Salla auto-delivers them to the webhook_url. So there is no
subscribe call to check. Auto-check: salla_apps action=get shows a webhook_url set, and
the partner's handler covers the full app.subscription.* / app.trial.* family (→
salla-app-billing Step 2). This only proves the receiver is wired at the platform level —
not that the handler code is deployed and working. So also verify a real round-trip: fire
a test subscription event on a demo store and confirm the app provisions/revokes correctly (→
salla-live-testing).
Then user-confirm: ask the partner to confirm the handlers are deployed and tested for
the FULL lifecycle — provision on started,
re-point entitlement on renewed, revoke on expired/canceled (verify item_type: plan vs
addon), and for external_recurring addons the renew API is wired (→ salla-app-billing).
If not coherent, do NOT submit — keep the saved draft, hand off to salla-app-billing /
salla-addon-purchase to implement the billing cycle, then re-check and re-gate. Only a
coherent billing cycle clears this gate.
- Ask the partner to REVIEW, then send the publish request. Give them their real Portal
link — substitute the app's actual id, never the placeholder:
https://portal.salla.partners/apps/{app_id}/publish
(e.g. https://portal.salla.partners/apps/1234567/publish). Tell them to review the draft
there. To go to review, either the partner clicks submit one-click in the Portal, or
— only after they explicitly confirm — you call
app_publish action=send_publish_request with confirm: true. Hard rule: never send the
request before a clean validate, the partner being shown the link, and their confirmation.
Gate: "All sections validated + saved as a draft; for paid pricing (recurring plan or any
addon) the billing cycle is wired + confirmed (else kept as a draft until it is); the partner was
given the real /publish link and asked to review — and send_publish_request fires ONLY after
they confirm (or they submit one-click themselves)?"
Done (for the agent) means validate returns a valid publication and the partner has reviewed
the real /publish link — the app goes to review only on their one-click submit or their
explicit confirmation (then send_publish_request confirm=true), never before.
Red Flags
| Tempting thought |
Why it's wrong |
| "Validate succeeded, so I'll send the publish request to finish the job." |
send_publish_request is HARD-GATED — allowed only after the partner reviewed the draft at the /publish link and explicitly confirmed (Step 7). Don't send on a clean validate alone. |
"I'll just give them the …/apps/{app_id}/publish template; they'll fill it in." |
Substitute the real app id (Step 7). A placeholder link sends the partner nowhere and they can't submit. |
| "No images provided — I'll skip them / drop in a placeholder and move on." |
Ask the user first; if they decline, use a clearly-marked placeholder and tell them to replace it in the Portal before submitting. |
| "I'll write a description / pick a category / set a price to reach readiness." |
These are the partner's business decisions. Ask them, suggest Salla-grounded options, and fill from their answers — never fabricate or blind-fill a section to pass the gate. |
| "Validate passed and the app sells a subscription/addon — I'll submit." |
Paid pricing requires the billing cycle wired and confirmed first (Step 6b). If it isn't, keep the saved draft and implement the cycle (salla-app-billing / salla-addon-purchase) before submit. |
"The webhook_url is set, that's enough to go to review." |
Receiving ≠ handling. The app.subscription.* / app.trial.* events auto-deliver to the webhook_url (app events — no subscribe call needed), so the auto-check proves the receiver is wired at the PLATFORM level, not that the handler is deployed and working. The app must provision on started, re-point on renewed, revoke on expired/canceled (check item_type), call the renew API for external_recurring, and be verified by a real test event (salla-live-testing) before submit (Step 6b). |
| "I need to subscribe the app to its subscription/trial events first." |
You don't — those are app events, auto-delivered to the webhook_url because the app is subscribed to its own app events by default. There's no subscribe step to check; salla_events action=subscribe is for store events (order.*, product.*). The billing gate is webhook_url set + handler covers the full family (Step 6b → salla-app-billing Step 2). |
| "All sections are complete, so I'll submit." (app not publishable / unverified) |
Check the prerequisites first: can_publish must be true and the partner's account verified at portal.salla.partners/account (an unverified account fails submit with id_verification); an SMS communication app needs a CITC certification on the verification form (salla-communication-app). |
"I'll reuse the sub_category_id call's type result for main_category_id too." |
main_category_id is NOT type-scoped — it always comes from that same salla_reference action=categories call's main_categories (category type app_impact, one shared "App Theme"/"App Impact" list for every app type). The categories array is a THIRD, separate list (category type app). Neither is the sub_categories tree used for create's sub_category_id (step-basic-information.md). |
1---2name: salla-publication-consistency3description: Use when a PUBLIC Salla app is going to review (App Store listing): the master skill for stepwise publication via app_publish. It reads the current draft, routes each step to its owner, and HARD-GATES sending to admin review behind a clean validate plus the partner's explicit confirmation. Private apps publish from the Portal — see salla-app-builder. Hand-offs: listing/images → salla-app-ui-builder; pricing + billing cycle → salla-app-billing; scopes → salla-app-auth; webhooks → salla-webhooks; settings → salla-app-settings.4---56# Salla Publication Consistency78**Scope: PUBLIC apps (App Store listing).** Private apps don't use this stepwise flow and9have no MCP publish action — the partner sends their publish request from the app-details10page `https://portal.salla.partners/apps/{app_id}` (see **salla-app-builder**).1112## Before you can publish — prerequisites1314Check these first; they gate the whole flow regardless of the sections:1516All three are readable from app details — `app_publish action=get` (or `salla_apps action=get`)17returns `can_publish`, `requires_citc`, and `can_have_free_plan`. Check them before filling:18191. **The app must be publishable.** `can_publish` must be `true`. If `false`, the app cannot be20 submitted.212. **The partner's account must be verified.** Before publishing, the partner verifies their22 account at **https://portal.salla.partners/account**. An unverified account blocks submission23 (validate/submit returns an `id_verification` error).243. **CITC certification (SMS communication apps).** When `requires_citc` is `true`, the partner25 must upload a CITC certification on the account verification form before publishing (Saudi26 regulatory requirement) → **salla-communication-app**.2728Prepare a Salla app for review via `app_publish` (base `/app/{id}/publication`): a29readiness-driven loop, not one bulk call. Fill sections one at a time; the **server** decides30when the draft is ready.3132**open → get (read the current draft) → (set `<section>` → readiness)\* → validate → partner reviews the /publish link → send_publish_request**3334`app_publish action=validate` validates every section for completeness/correctness and35**saves a DRAFT**, returning a valid publication — it does **not** submit. **Sending the app36to Salla review is HARD-GATED:** after a clean `validate`, surface the partner's real37`/publish` link (id substituted) and ask them to **REVIEW** the draft. Only then does it go to38review — either the partner **submits one-click** in the Portal, **or**, after they39**explicitly confirm**, the agent calls `app_publish action=send_publish_request` with40`confirm: true`. Never send the request before a clean validate **and** the partner reviewing41the link **and** confirming.4243Drive off the readiness checklist returned by `open` and every `set`: it marks each section44`complete` or, for incomplete ones, lists the exact `missing` fields. Fix the one section it45points at, re-check, repeat. `validate` runs the same server-side gate and returns **422 +46missing sections** if anything is incomplete, and **saves the draft** when every section is47complete.4849## The loop (`app_publish`, base `/app/{id}/publication`)5051| action | verb | body | does |52| ---------------------- | ---- | ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |53| `open` | POST | — | Create the draft + return the per-section readiness checklist; opening also makes `app_page_builder` available for listing content. |54| `get` | GET | — | Read-only: the FULL current draft (`publication_last_save` — every saved value) + scopes + webhooks. RESUME/REVIEW entry — readiness shows what's MISSING, `get` shows what's THERE. |55| `readiness` | GET | — | Re-fetch the checklist: which sections are `complete` + the exact `missing` fields. Pure read. |56| `set` | PUT | `{ section, ...data }` | Write ONE section; only the fields you pass are touched; returns updated readiness. |57| `validate` | PUT | — | Validate all sections + **save the DRAFT**; returns a valid publication. Incomplete → **422 + missing sections**. Does **not** submit. |58| `send_publish_request` | PUT | `confirm:true` | Send the app to Salla review — **HARD-GATED**: only after `validate` + the partner reviewed the `/publish` link + **confirmed**. Without `confirm:true` it does NOT submit (returns the link). |5960Listing content (name, description, logo, screenshots, benefits) is authored with61`app_page_builder` → **salla-app-ui-builder**.6263## Read the current draft first (resume / review)6465Before filling or fixing anything — **especially when resuming a draft or asked to review one** —66read it with `app_publish action=get`. It returns `publication_last_save` (every saved section67value) plus the app's `scopes` and `webhooks`. `readiness` tells you what's **missing**; `get`68tells you what's **already there** — never re-ask the partner for a value the draft already holds,69and never blind-overwrite a section you haven't read.7071## Validation is step-by-step7273Three layers, in order — don't defer everything to one bulk check at the end:74751. **`set` validates FORMAT per section** (server `PublicationSectionRequest`: e.g.76 `short_description` 50–200, `video_url`/URLs well-formed, plan numeric rules). A `set` that77 returns **422** is THIS section's validation error — the tool surfaces the field messages; fix78 them and `set` again before moving on.792. **`readiness` reports COMPLETENESS** — which sections are `complete` and the exact `missing`80 (blank) fields. Completeness ≠ correctness: a field can be present-but-invalid (caught by81 `set`) or present-but-blank (caught by `readiness`).823. **`validate` is the final CROSS-FIELD + requiredness gate** over the whole draft — it saves83 the DRAFT when clean, or returns 422 + the still-missing sections.8485The loop: `set <section>` → if 422, fix this section → `readiness` → next. Catch each section's86errors as you write it, not in one end-of-flow `validate`.8788## Step map — this skill is the master router8990Each publication step routes to the skill that owns its modelling, and each has a **reference**91(data retrieval → submission schema → how to submit):9293| Step (publication section) | Owner skill | Reference |94| -------------------------------------------------------- | ------------------------------- | ----------------------------------------------------------------- |95| Basic Information | this skill | [step-basic-information.md](references/step-basic-information.md) |96| App Config (scopes/webhooks — not a `set` section) | salla-app-auth + salla-webhooks | [step-app-config.md](references/step-app-config.md) |97| Features (banner / embedded image; screenshots/benefits) | salla-app-ui-builder | [step-features.md](references/step-features.md) |98| Pricing | salla-app-billing | [step-pricing.md](references/step-pricing.md) |99| Contact Information | this skill | [step-contact.md](references/step-contact.md) |100| Service Trial / Review | this skill + salla-app-billing | [step-service-trial.md](references/step-service-trial.md) |101102> **`app_page_builder` is the App Store LISTING/PROFILE page builder** — the public app profile103> (rating, plans, install button). It is publication-coupled: it pulls images/text/plans from the104> draft, can be edited against a **draft** publication, and its **images and content override the105> publication's**. It is NOT the post-install embedded dashboard UI (→ **salla-embedded-app** /106> **salla-embedded-ui**).107108## The 5 sections at a glance109110Pass `section` + only the fields you're writing (localized fields take `{ ar, en }`; image fields111an integer id from `salla_upload`; multi-value fields an array). Per-field **schema, server rules,112and a worked `set` example** are in each step's reference (Step map above) — read it before writing.113114| Section | Sets (summary) | Reference |115| --------------------- | ---------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------- |116| `basic_information` | short_description, main_category_id (App Theme/Impact), categories, **video_url (required)**, demo_url, search_terms, supported_countries | [step-basic-information.md](references/step-basic-information.md) |117| `features` | banner, embedded_image (screenshots/benefits → `app_page_builder`) | [step-features.md](references/step-features.md) |118| `pricing` | plan_type + that type's plan/addon/once fields | [step-pricing.md](references/step-pricing.md) → **salla-app-billing** |119| `contact_information` | contact_method, notification/submission/support contacts, website_url, policy_url, faq_url | [step-contact.md](references/step-contact.md) |120| `service_trial` | service_link, trial_username, trial_password, trial_description, update_note (updates only) | [step-service-trial.md](references/step-service-trial.md) |121122## Route elsewhere — not set via `app_publish`123124These live outside the publication sections. Finalize each in its own tool, then re-check125readiness:126127| What | Where |128| ------------------------------------------------------------------------- | ---------------------------------------------------- |129| Listing content: `name`, `description`, `logo`, `screenshots`, `benefits` | `app_page_builder` → **salla-app-ui-builder** |130| OAuth scopes (request the minimum needed) | `salla_scopes` → **salla-app-auth** |131| Webhook url / signing secret / headers | `salla_apps action=connect` → **salla-webhooks** |132| Store-event subscriptions (`order.*`, `product.*`, …) | `salla_events action=subscribe` → **salla-webhooks** |133| Merchant settings FORM | `salla_settings` → **salla-app-settings** |134135Security review checks belong to the owning skills: minimum OAuth scopes →136**salla-app-auth**; webhook signature verification + authenticated interfaces →137**salla-webhooks**; secret handling → keep credentials test-only, redacted, never plaintext.138139The draft is a snapshot taken at `validate`, not a live mirror. Settings snapshot when the140draft is saved and are matched against the live state, so finalize the external pieces141**before** you `validate` and do settings last:142143- Finalize the settings form (`salla_settings`) before `validate` — whatever is live when144 the draft is saved is what the partner submits.145- **Communication apps** must declare supported features146 (`salla_settings action=set_features`) before `validate`, or the gate blocks.147- After changing any external piece (scopes, webhook, events, settings, builder content),148 re-run `readiness` (re-`open` if needed) and `validate` again before handing off.149150## First-time publish — guided onboarding, not a blind fill151152The publication sections carry the partner's **business decisions** — listing copy,153categories, pricing/plans/trial, contact, supported countries, screenshots/benefits. For a154**first-time publish**, walk the partner through the sections **one at a time**; fill each155from **their answers**, never invent or auto-fill. At each section:156157- **Ask the right questions** for that section, e.g.:158 - `basic_information` → value prop / short description, main category + sub-categories,159 supported countries, a demo/video URL? (`video_url` is required — if they have none, point160 them to the educational-video guide: https://salla.dev/tutorial/educational-video-guidelines-apps-ar/)161 - `pricing` → free or paid? one-time, subscription plans, or addons? a trial?162 - `contact_information` → support email/phone, policy + FAQ URLs?163 - App Information (`app_page_builder`) → name, description, logo, ≥3 screenshots, benefits?164- **Suggest valid options grounded in Salla** so the partner picks from real choices, not165 guesses: real categories from `salla_reference action=categories`; the pricing models166 Salla billing supports (free / one-time / subscription plans + addons + trial →167 **salla-app-billing**); supported-country options from `salla_reference action=countries`.168- **Fill from the answers**, then run `app_publish action=readiness` to see what's still169 missing, and move to the next section.170171**Suggest the publication-window monetization features.** First publish is the moment to set172these — surface them as grounded **opt-in** options (never auto-add), each routing to173**salla-app-billing** / **salla-addon-purchase**:174175- **Addons** — extra in-app purchases on top of any plan.176- **Trial** — a free `plan_trial` (days) and/or the reviewer service trial.177- **Promotions** — time-boxed plan discounts (`promotions[]`).178- **Comparison matrix** — `plan_features[]` to compare plans side by side.179- **Recommended / highlighted plan** — `recommended`, `is_compare_included`.180- **Strikethrough pricing** — `one_time_old_price` (once) or a plan's `old_price`.181- **Adjustable quantities** — `plan_additional_features` (once) / per-plan `additional_features`.182- **Churn-prevention** — `unsubscribe_reward`, `unsubscribe_email_reward`.183184Note which are publication-window only; per-merchant/tailored plans come **later** via App185details → Custom Plans. Any paid choice triggers the billing-cycle gate (below).186187This composes with the **image-asset rule** below (ask for images; on skip → clearly-marked188placeholders + tell the partner to replace them).189190**Gate:** "Each section filled from the partner's answers — listing copy, categories,191pricing, contact — not auto-invented?"192193## Listing images — ask first, placeholder only with a heads-up194195The listing needs **real** images: `logo` + **≥3 screenshots** (App Information),196`banner` + `embedded_image` (App Features). These are owned by197**salla-app-ui-builder** — read it for the fields, dimensions, and the generate-then-upload198recipe. The discipline here:199200- **If you don't have the images, ASK the user to provide all the required ones.** Don't201 invent a real-looking image and don't silently skip them — this is verify-don't-invent.202- **If the user skips or declines, proceed with clearly-marked default placeholders** so the203 draft still validates — then **explicitly tell the partner to replace the placeholders in204 the Portal before they do the one-click submit**. Never present a placeholder as final.205206**Gate:** "Every listing image is a real partner asset — or a placeholder the partner has207been explicitly told to replace before submitting?"208209## Pre-publication sequence (in order)2102111. **Open** the draft (`action=open`); read the initial readiness checklist.2122. `set` each incomplete section's `data`, re-checking `readiness` after each — **one section213 at a time**, driven off the returned `missing` list.2143. Author builder content (screenshots, benefits, listing name/description/logo) via215 `app_page_builder` → **salla-app-ui-builder** so `features` completes — applying the216 image-asset rule above for any missing image.2174. Finalize the settings form (`salla_settings`); for communication apps run218 `salla_settings action=set_features`.2195. Re-run `readiness` — confirm all 5 sections read `complete`.2206. **Validate + save the draft** (`action=validate`). On **422**, `set` the missing sections221 and `validate` again until it returns a valid publication.222 6b. **Billing-cycle gate — paid pricing only, BEFORE submit.** If the draft's pricing has a223 recurring plan or any addon, the app must handle the subscription lifecycle before it can go to224 review. The lifecycle rides on **app events** (`app.subscription.*` / `app.trial.*`), which the225 app is subscribed to by default — Salla auto-delivers them to the `webhook_url`. So there is **no226 subscribe call to check**. **Auto-check:** `salla_apps action=get` shows a `webhook_url` set, and227 the partner's handler covers the full `app.subscription.*` / `app.trial.*` family (→228 **salla-app-billing** Step 2). This only proves the receiver is wired at the **platform** level —229 **not** that the handler code is deployed and working. So also **verify a real round-trip:** fire230 a test subscription event on a demo store and confirm the app provisions/revokes correctly (→231 **salla-live-testing**).232 **Then user-confirm:** ask the partner to confirm the handlers are **deployed and tested** for233 the FULL lifecycle — provision on `started`,234 re-point entitlement on `renewed`, revoke on `expired`/`canceled` (verify `item_type`: plan vs235 addon), and for `external_recurring` addons the renew API is wired (→ **salla-app-billing**).236 **If not coherent, do NOT submit** — keep the saved draft, hand off to **salla-app-billing** /237 **salla-addon-purchase** to implement the billing cycle, then re-check and re-gate. Only a238 coherent billing cycle clears this gate.2397. **Ask the partner to REVIEW, then send the publish request.** Give them their real Portal240 link — substitute the app's actual id, never the placeholder:241 `https://portal.salla.partners/apps/{app_id}/publish`242 (e.g. `https://portal.salla.partners/apps/1234567/publish`). Tell them to review the draft243 there. To go to review, **either** the partner clicks submit one-click in the Portal, **or**244 — only after they **explicitly confirm** — you call245 `app_publish action=send_publish_request` with `confirm: true`. **Hard rule:** never send the246 request before a clean `validate`, the partner being shown the link, and their confirmation.247248**Gate:** "All sections validated + saved as a draft; for paid pricing (recurring plan or any249addon) the billing cycle is wired + confirmed (else kept as a draft until it is); the partner was250given the real `/publish` link and asked to review — and `send_publish_request` fires ONLY after251they confirm (or they submit one-click themselves)?"252253Done (for the agent) means `validate` returns a valid publication and the partner has reviewed254the real `/publish` link — the app goes to review only on their one-click submit or their255explicit confirmation (then `send_publish_request confirm=true`), never before.256257## Red Flags258259| Tempting thought | Why it's wrong |260| --------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |261| "Validate succeeded, so I'll send the publish request to finish the job." | `send_publish_request` is HARD-GATED — allowed only after the partner reviewed the draft at the `/publish` link and explicitly confirmed (Step 7). Don't send on a clean validate alone. |262| "I'll just give them the `…/apps/{app_id}/publish` template; they'll fill it in." | Substitute the **real** app id (Step 7). A placeholder link sends the partner nowhere and they can't submit. |263| "No images provided — I'll skip them / drop in a placeholder and move on." | Ask the user first; if they decline, use a clearly-marked placeholder **and** tell them to replace it in the Portal before submitting. |264| "I'll write a description / pick a category / set a price to reach readiness." | These are the partner's business decisions. Ask them, suggest Salla-grounded options, and fill from their answers — never fabricate or blind-fill a section to pass the gate. |265| "Validate passed and the app sells a subscription/addon — I'll submit." | Paid pricing requires the billing cycle wired **and** confirmed first (Step 6b). If it isn't, keep the saved draft and implement the cycle (salla-app-billing / salla-addon-purchase) before submit. |266| "The `webhook_url` is set, that's enough to go to review." | Receiving ≠ handling. The `app.subscription.*` / `app.trial.*` events auto-deliver to the `webhook_url` (app events — no subscribe call needed), so the auto-check proves the receiver is wired at the PLATFORM level, not that the handler is deployed and working. The app must provision on `started`, re-point on `renewed`, revoke on `expired`/`canceled` (check `item_type`), call the renew API for `external_recurring`, and be verified by a real test event (salla-live-testing) before submit (Step 6b). |267| "I need to subscribe the app to its subscription/trial events first." | You don't — those are **app events**, auto-delivered to the `webhook_url` because the app is subscribed to its own app events by default. There's no subscribe step to check; `salla_events action=subscribe` is for store events (`order.*`, `product.*`). The billing gate is `webhook_url` set + handler covers the full family (Step 6b → salla-app-billing Step 2). |268| "All sections are complete, so I'll submit." (app not publishable / unverified) | Check the prerequisites first: `can_publish` must be `true` and the partner's account verified at `portal.salla.partners/account` (an unverified account fails submit with `id_verification`); an SMS communication app needs a CITC certification on the verification form (salla-communication-app). |269| "I'll reuse the `sub_category_id` call's `type` result for `main_category_id` too."| `main_category_id` is NOT type-scoped — it always comes from that same `salla_reference action=categories` call's `main_categories` (category type `app_impact`, one shared "App Theme"/"App Impact" list for every app type). The `categories` array is a THIRD, separate list (category type `app`). Neither is the `sub_categories` tree used for create's `sub_category_id` (step-basic-information.md). |