<!-- OpenSmartRoute: Marketplace. https://osr-web.kindrock-caaab1a2.centralindia.azurecontainerapps.io/docs/MARKETPLACE -->
# Marketplace

The marketplace is the part of the [hosted platform](https://osr-web.kindrock-caaab1a2.centralindia.azurecontainerapps.io/docs/PLATFORM.md) where routing building blocks are
shared: **agents, skills, personas, prompts, tools, model profiles and stack templates**, with
versions, ratings and a short review lifecycle. Every listing is a plain YAML or JSON document that
the OpenSmartRoute SDK, CLI and hosted API understand, so what you install is what you run; there
is no marketplace-only format.

Browsing, installing, rating and publishing prompts and personas work from the website without
writing code. Templates, skills and agents come with copy-paste snippets for the CLI and the SDK.

## What is on it

| Kind | What it is | Manifest |
|---|---|---|
| **Template** (stack) | A complete routing setup - models, rules, weights - in one file. Apply it and you have a working router. | `kind: stack` document ([`opensmartroute.stack`](https://osr-web.kindrock-caaab1a2.centralindia.azurecontainerapps.io/docs/REFERENCE.md#opensmartroutestack)) |
| **Agent** | A multi-step worker the router can hand a whole task to. | Open Capability Manifest ([spec](https://osr-web.kindrock-caaab1a2.centralindia.azurecontainerapps.io/docs/ocm.md)) |
| **Skill** | An Agent-Skills package (`SKILL.md`) that is also a routable target. | OCM, generated from the `SKILL.md` |
| **Persona** | A system prompt with a voice, a domain and a complexity band. | OCM with `kind: persona` |
| **Tool** | A function or MCP tool with typed parameters. | OCM with `kind: tool` |
| **Model profile** (llm) | A model with cost, latency, context window and quality prior. | OCM with `kind: llm` |
| **Prompt** | A reusable prompt with `{{variables}}`. | `{"prompt": "...", "variables": [...]}` |

Every listing has a **slug** (its address, `/marketplace/<slug>`), a **version** (`1.2.3`), a
publisher, a licence, tags, a Markdown description, an install count and a star rating.

## Finding and installing

1. Open **Marketplace** in the top navigation. Filter by kind, tag, price or search; sort by
   popular, top rated, newest or name.
2. Open a listing. The **About** tab is the publisher's description; **Use it** shows ready-made
   snippets (CLI, Python, HTTP); **Manifest** is the raw document; **Reviews** lists ratings.
3. Click **Install** (free) or **Buy** (paid). You need an account - signing up is free. Free
   installs complete at once; paid ones go through the checkout and are recorded on your account
   when the payment completes.
4. Everything you installed is listed under **Dashboard -> Marketplace -> Installed by you**, together with
   the version you installed and whether a newer one exists.

Installing records the listing on your account and returns the manifest and snippets. Nothing is
executed on your behalf.

### Using an installed template

```bash
osr stack apply registry://support-desk@1.0.0                      # build and summarise (hosted marketplace)
osr stack apply registry://support-desk --route "Refund for order 42?"
osr stack apply registry://support-desk --out stack.yaml             # flatten & keep
osr stack apply registry://support-desk --registry https://<platform>  # a self-hosted marketplace
```

`registry://` references resolve against the marketplace the CLI is configured for (`OSR_MARKETPLACE_URL`,
by default the hosted platform); `--registry` points one command at another deployment.

Or reference it from your own stack and let imports do the work:

```yaml
osr: "1"
kind: stack
name: my-desk
imports:
  - registry://support-desk@1.0.0
targets:
  - id: llm-onprem
    kind: llm
```

`osr stack validate my-desk.yaml`, `osr stack plan my-desk.yaml --against deployed.yaml` and
`osr stack apply my-desk.yaml` are the Terraform-style verbs: check, diff, apply.

### Using an installed agent, skill, persona, tool or model

**On the hosted platform nothing more is needed.** Installing an agent, skill, persona, tool, subagent or
command puts it into your workspace's routing pool: from the next request on, `POST /api/v1/route` with
`plan: true` (and the OpenAI-compatible endpoints with plan composition) picks the installed skill or persona
for the matching slot when it fits the request better than the catalogue's, and an installed primary competes
for the answer itself. `GET /api/v1/targets` lists your installs with `source: marketplace`; `plan_mode`
(`sequential` / `parallel`) decides whether several installed skills run one after another or at the same time
([platform guide](https://osr-web.kindrock-caaab1a2.centralindia.azurecontainerapps.io/docs/PLATFORM.md#2-route-a-request)). Other workspaces never see your installs. Try it in the
playground: install a skill, ask a matching question with *Build a plan* on, and the plan's *prepare* stage
names it. An install is seen by every API replica on the very next request - no waiting for a cache.

**Skills, personas and prompts you never installed can help too.** Every plan also offers the router the
published skills, personas and prompts whose listing matches the request - the same vocabulary the router
extracts (its domains and salient words) is searched in the listing's name, summary, tags and domains, and
only a listing that works in one of the request's domains qualifies - so the marketplace shapes an answer
without anyone browsing it first. A prompt joins as an instructions-only skill when it takes its input through
at most one `{{variable}}` (the placeholder stands for the user's message); one with several variables is a
template for a person to fill. The router's own strategies and quality floor still decide whether such a
companion is used; a discovered one never answers on its own, and the decision names it under `marketplace`
with `installed: false`. What may be discovered is a matter of trust: by default only listings an operator has
marked **verified** (the deployment's own seeded skills, personas and prompts are), because a discovered
skill's instructions reach the prompt of a workspace that did not choose it. A workspace widens or narrows that
with the `marketplace_discovery` policy key - `verified` (default), `all` (every published listing) or `off`
(installs only) - and the operator sets the deployment default with `OSR_PLATFORM_MARKETPLACE_DISCOVERY`.
Agents and tools are never discovered: they *execute* (a call to the publisher's endpoint with the request in
it), so they join the pool through an install only.

Installed agents and tools do not only get picked, they **run**: when a listing's manifest declares a public
`https` endpoint with protocol `a2a` (an agent card), `http` (a JSON agent or workflow endpoint) or `mcp` (a
Streamable-HTTP MCP server), the platform binds it - an executed request that lands on the listing calls the
agent, or lists the MCP server's tools once and runs the one the request names (`context.tool`), the only one
there is, or the closest match, with `context.tool_arguments` as its arguments. `GET /api/v1/targets` shows
`executable: true` for those; a listing whose endpoint the platform may not call (a private address, a
protocol that needs credentials such as `openai` or `queue`) stays routable but not executable and carries
the reason in `metadata.endpoint_unbound`, which the dashboard's *Installed* tab shows as a badge. Skills and
personas need no endpoint: their instructions run through the model that answers. An installed **model card**
(an `llm` listing) needs your own key: map it to one of your providers on the *Providers* page - it is offered
there next to the catalogue with the listing's upstream model id prefilled - and it runs on your account like
any other target; `/v1/models` lists it and a chat may name it (`model: <slug>`).

In your own code an OCM manifest becomes a target with one call:

```python
from opensmartroute import Router
from opensmartroute.ocm import bind_endpoint, target_from_capability

manifest = {...}  # GET /api/v1/registry/<slug>/manifest
router = Router([bind_endpoint(target_from_capability(manifest))])  # http / a2a / mcp endpoints get a handler
```

Prompts are text: read `manifest["prompt"]` and fill in `manifest["variables"]`.

The package has a client for the whole loop - search, load, keep in sync - so a router follows the
marketplace (which itself refreshes from its public sources every day) without manual downloads:

```python
from opensmartroute import Router, TargetRegistry
from opensmartroute.adapters import MarketplaceClient

mk = MarketplaceClient()  # https://api.opensmartroute.ai
skills = mk.targets("sql reporting", kind="skill", limit=5)  # RouteTargets from the listings' manifests
agents = mk.targets(kind="agent", tag="a2a", limit=10)  # A2A agent cards, endpoints attached
router = Router(TargetRegistry([*my_models, *skills, *agents]))

report = mk.sync(router.registry, kind="skill", tag="skill-md", limit=50)  # run daily: adds new, updates changed
print(report.added, report.updated)
```

`MarketplaceClient.search` returns `Listing` rows (slug, kind, version, installs, rating, source);
`target(slug)` loads one manifest; `sync` upserts by id and replaces a target when its version, text,
instructions or endpoints changed upstream. Only routable kinds (agent, skill, persona, tool, llm) become
targets; templates are stacks - import them with `registry://slug@version`.

`sync` is safe to run forever: the `SyncReport` lists `added`, `updated`, `skipped` (no loadable manifest),
`removed`, `kept` and `seen`. A followed listing the marketplace answers 404 / 410 for is **removed** from the
registry; a synced target you edited locally (the sync stamps a fingerprint in `metadata["marketplace"]`) is
**kept** as it is until you pass `force=True`; `prune=True` also removes the targets this marketplace delivered
earlier that no longer match the search (`OSR_MARKETPLACE_PRUNE=1` does the same for the server-side
`MarketplaceSync` below). The same from the CLI:

```bash
osr marketplace search "sql reporting" --kind skill            # list matching listings
osr marketplace pull acme-skills-sql-reporting --out skills.json # manifests as an OCM catalogue file
osr marketplace sync --kind skill --tag skill-md --limit 50 --targets-file marketplace.json   # daily cron job
osr --targets marketplace.json route "Write the monthly revenue report" --plan
```

`sync --targets-file` creates or updates a catalogue file every routing command loads with `--targets`,
so a scheduled `osr marketplace sync` keeps a self-hosted router on the marketplace's daily state. The file
keeps each manifest's endpoints, and `load_targets` binds them again on the next start (`http`, `a2a`, `mcp`),
so an agent or MCP tool the router picks from that catalogue *runs* - the process restart loses nothing.

### Following the marketplace from `osr serve`

A running server can follow the marketplace by itself - no cron, no restart: `MarketplaceSync` upserts the
followed listings into the served catalogue every `OSR_MARKETPLACE_REFRESH_S` seconds, so a skill published
(or re-versioned) upstream is routable on the next tick.

```bash
osr -t targets.yaml serve --marketplace-refresh 86400                    # once a day
OSR_MARKETPLACE_REFRESH_S=86400 OSR_MARKETPLACE_KINDS=skill,tool osr -t targets.yaml serve
OSR_MARKETPLACE_QUERY="sql reporting" OSR_MARKETPLACE_SLUGS=acme-skills-sql-reporting ...
```

`OSR_MARKETPLACE_URL`, `OSR_MARKETPLACE_KINDS` (default `skill,tool`), `OSR_MARKETPLACE_QUERY`,
`OSR_MARKETPLACE_TAG`, `OSR_MARKETPLACE_LIMIT` (per kind) and `OSR_MARKETPLACE_SLUGS` choose what is
followed (`MarketplaceSettings`). `GET /stats` reports the sync (`marketplace`: last run, added / updated,
last error) and `POST /marketplace/sync` runs one now (a write path; read-only tokens are refused). In
Python, `MarketplaceSync(router.registry).start()` does the same in any process.

### From the IDE (MCP)

The local MCP server (`osr -t targets.yaml mcp`) carries `marketplace_search` and `marketplace_get` backed by
the marketplace in `OSR_MARKETPLACE_URL` (`--marketplace-url` to point elsewhere, `--no-marketplace` to leave
them out). `marketplace_get` with `install: true` also loads the listing into the running router's catalogue,
so an assistant can find a skill and route to it in the same conversation. To try a listing without installing
it, `route` takes `marketplace: [slug, ...]`: those listings join the candidates for that one request (and fill
the plan's slots) and the result names them under `marketplace_candidates`. When something in the catalogue can
run - a model behind `OSR_LLM_BASE_URL`, an agent or MCP server with a bound endpoint - the local server also
offers `ask`, which routes and runs in one call.

The HTTP endpoints are public for published listings:

```
GET /api/v1/registry?kind=template&q=support&sort=popular
GET /api/v1/registry/{slug}
GET /api/v1/registry/{slug}/manifest?version=1.0.0
GET /api/v1/registry/{slug}/reviews
```

`GET /api/v1/registry` filters with `q` (free text, 80 characters), `kind`, `tag` (one exact tag, 64
characters), `price=free|paid`, `featured=true|false` and orders with `sort=popular|newest|name|rating`;
`q` and `tag` are matched literally. Pages are `limit` (1-100) and `offset` (at most 10 000); the whole
catalogue is paged through `GET /api/v1/registry/sitemap` instead. Each page carries the kind counts and
top tags of the catalogue, refreshed every few minutes.

## Ratings and reviews

Anyone with an account can rate a listing from 1 to 5 stars and leave a short review. One review per
account per listing (writing again replaces the previous one). Reviews from accounts that installed
the listing are marked as such. A listing shows its average rating, the number of ratings and a star
histogram.

### Leaderboard

`/rankings/marketplace` ranks the published listings per kind - most installed, best rated or newest -
from `GET /api/v1/rankings/listings?kind=<kind>&sort=installs|rating|newest`. *Best rated* is a
Bayesian average: the mean rating shrunk towards 3.5 by the weight of five ratings, so a listing with
one five-star review sits below one with forty four-star reviews until more ratings arrive.
`GET /api/v1/rankings/listings/kinds` gives the published listings, installs and reviews per kind.
Publishers appear by display name only.

## Publishing

**Marketplace -> Publish** walks through four steps: choose the kind, describe it, paste or write
the manifest, review and submit. Prompts and personas need no code at all; for templates the wizard
offers a starter you can edit. You can also post the same payload to `POST /api/v1/registry`.

### The lifecycle

```
draft  ->  review  ->  published  ->  archived
  ^          |
  +--- rejected
```

- **Draft** - only you can see it. Edit anything, including the manifest.
- **Submit** - the manifest is validated (OCM schema, stack schema or prompt shape). Free listings
  are published immediately; paid listings go to **review**. (Operators can require review for
  everything with `OSR_PLATFORM_REGISTRY_AUTO_PUBLISH=false`.)
- **Review** - a platform reviewer approves or rejects with a note. Rejected listings go back to
  you with the note; fix and submit again.
- **Published** - live. Name, summary, description, tags, price and licence stay editable. The
  manifest is frozen: ship a **new version** instead, with a changelog. Old versions remain
  downloadable (`?version=`).
- **Archived** - hidden from the marketplace; existing installs keep working. You can restore it as
  a draft.

Your listings, their status and any reviewer note are under **Dashboard -> Marketplace -> Published
by you**; each one has an edit page with the version form.

### Paid listings

Set a price in USD when publishing (paid plans, Pro and above). Buyers pay through the platform's
checkout; the purchase is confirmed by the payment webhook and the listing is then recorded on their
account. Free plans can publish a limited number of free listings.

### Writing a good listing

- A one-line summary that says what it does and for whom.
- A description that covers what it needs (models, keys, data) and how to use it - Markdown, no
  raw HTML or images.
- Tags people would search for (`support`, `pii`, `sql`, `on-prem`).
- Test templates locally first: `osr stack validate stack.yaml`.
- Skills: keep `SKILL.md` front matter complete (`name`, `description`); `osr skills validate`
  checks it.

## Built-in content

Every platform ships seeded with the repository's own Agent-Skills packages, a starter template, the
`examples/stack.yaml` support-desk template, a persona and a couple of prompts, all published by
`OpenSmartRoute` and marked verified. Set `OSR_PLATFORM_REGISTRY_SEED=false` to run an empty
marketplace.

## Imported catalogue

The hosted marketplace also carries the public agentic ecosystem - Agent Skills, agent instructions, prompt
files, slash commands and workflows, chat modes, act-as roles and Fabric patterns, custom agents and subagents,
A2A agent cards, stack files, MCP servers, the reference LLM catalogue and routing stacks generated from it -
harvested and refreshed every day by
the platform itself, so **every** marketplace kind fills from public sources:

| Source | What is imported | Listing kind |
|---|---|---|
| [skills.sh](https://skills.sh) | every `SKILL.md` in each repository on the leaderboard (about 2 400 repositories) | `skill` |
| GitHub code search | the long tail of `SKILL.md` files (needs a GitHub token; optional) | `skill` |
| GitHub code search (`github-agents`) | every public `AGENTS.md` file - the cross-tool agent-instruction standard (needs a GitHub token; optional) | `skill` |
| GitHub code search (`github-instructions`, `github-cursor-rules`) | `.github/instructions/*.instructions.md` (Copilot custom instructions, `applyTo` kept as `metadata.applies_to`) and `.cursor/rules/*.mdc` (Cursor project rules, `globs`) (needs a GitHub token; optional) | `skill` |
| GitHub code search (`github-prompts`) | `.github/prompts/*.prompt.md` reusable prompt files: the frontmatter's `description`, `mode`, `model` and `tools`, the body as the prompt with `${input:name}` / `${selection}` turned into `{{name}}` placeholders declared as `variables` (needs a GitHub token; optional) | `prompt` |
| GitHub code search (`github-claude-commands`) | `.claude/commands/**/*.md` Claude Code slash commands: `description`, `argument-hint`, `allowed-tools`, `model`; `$ARGUMENTS` becomes `{{arguments}}` and `$1` ... `$9` become `{{arg1}}` ... (needs a GitHub token; optional) | `prompt` |
| GitHub code search (`github-cursor-commands`, `github-windsurf-workflows`, `github-codex-prompts`) | `.cursor/commands/*.md` (Cursor custom commands), `.windsurf/workflows/*.md` (Windsurf workflows) and `.codex/prompts/*.md` (Codex custom prompts), same conversion; slugs end in `-cursor-command`, `-workflow`, `-codex-prompt` (needs a GitHub token; optional) | `prompt` |
| GitHub code search (`github-chatmodes`) | `.github/chatmodes/*.chatmode.md` - a voice, rules and tool set layered on a model (needs a GitHub token; optional) | `persona` |
| Awesome ChatGPT Prompts (`awesome-prompts`) | every "Act as ..." role of the CC0 `prompts.csv` (the prompt is the persona's instructions, the first sentence its description); slugs `persona-<role>` | `persona` |
| Fabric (`fabric-patterns`) | every pattern's `system.md` (identity and purpose, steps, output format; MIT) from one repository tarball; slugs `fabric-<pattern>` | `persona` |
| GitHub code search (`github-custom-agents`) | `.github/agents/*.agent.md` custom agent definitions: instructions, `tools`, `model`, `handoffs` (needs a GitHub token; optional) | `agent` |
| GitHub code search (`github-claude-agents`) | `.claude/agents/*.md` Claude Code subagents: `name`, `description`, comma-separated `tools`, `model`; slugs `owner-repo-<name>-subagent` (needs a GitHub token; optional) | `agent` |
| GitHub code search (`github-a2a`) | public A2A agent cards (`.well-known/agent.json`): name, skills, capabilities and the `a2a` endpoint the router can call (needs a GitHub token; optional) | `agent` |
| GitHub code search (`github-stacks`) | OpenSmartRoute stack files anywhere on GitHub (`osr: "1"`, `kind: stack`, at least one target or import; Docker / Pulumi stacks and invalid documents are dropped by the stack validator); the manifest *is* the stack, so `registry://<slug>@<version>` imports it; slugs `stack-owner-repo-<file>` (needs a GitHub token; optional) | `template` |
| Generated from the model catalogue (`catalogue-stacks`) | ready-made routing stacks the platform derives from the reference LLM catalogue: one *tiers* stack per vendor with at least three priced models (cheapest / middle / strongest by intelligence index, complexity rules, cost-aware objective) and themed stacks - free models, reasoning, vision, open weights (up to four models each). The targets are the catalogue's `llm` capabilities with their OpenRouter endpoint, so a stack installs with no mapping; regenerated as the line-up and prices change; slugs `stack-<vendor>` / `stack-free-models` ... | `template` |
| [MCP registry](https://registry.modelcontextprotocol.io) | the latest active version of every server, with its remote endpoints and packages | `tool` |
| Reference model catalogue (`models`) | every hosted LLM of the platform's model catalogue (the OpenRouter listing snapshot, `<data dir>/models.json` when the rankings loop has refreshed it): vendor, prices per million tokens (`cost.usd_per_1k_tokens` is the input/output average), context window, modalities, tool calling, open weights, the OpenAI-compatible endpoint `https://openrouter.ai/api/v1` and a quality prior from the Artificial Analysis intelligence index when published | `llm` |

Imported listings are published under the upstream owner's name, are not marked verified, and carry
`metadata.source` in their manifest (`provider`, `repository`, `path`, `ref`, `url`) so the listing page
links back to the original file; the readme starts with the upstream install command
(`npx skills add owner/repo --skill name`). Copyright and licence stay with the author (`license` is
taken from the frontmatter when present). Domains are inferred from the description with the router's
own lexicon; the `skill-md` / `mcp` tags and the source name are added to the author's tags.

The harvest runs inside the platform on its own schedule (`OSR_PLATFORM_MARKETPLACE_REFRESH_S`; operators can
run it now from the console): it only fetches what the catalogue does not have yet, re-checks stale rows, and
publishes each source's new listings as soon as that source finishes. Slugs are `owner-repo-skill`
(`mcp-<server name>` for tools,
`model-<vendor>-<name>` for models, `owner-repo-<file>-<convention>` such as `acme-app-deploy-prompt` for
prompt / chat-mode / agent / instruction files, so two conventions sharing a file name stay distinct),
truncated with a short hash when longer than 63 characters.

Imported text is screened before it is stored: control characters are dropped and credential-shaped
strings an author left in a file (AWS access keys, GitHub / OpenAI / Slack / Stripe / Google API keys,
platform keys, JWTs, `PRIVATE KEY` blocks) are replaced with `[REDACTED <kind>]`, including documentation
placeholders - the original stays one click away upstream. Repositories that publish attack corpora
(poisoned, malicious or prompt-injection skill datasets) are never mirrored: the harvester skips them, the
cache replay drops rows cached earlier, and the admin import endpoint rejects them
(`source: repository publishes attack corpora and is not mirrored`).

For a platform whose SQLite file lives on a network share (the Azure Container Apps deployment mounts
Azure Files), publish tens of thousands of rows **offline**: build the database locally with
`publish --data-dir`, stop the API, replace `platform.sqlite3` on the share, start it again. Sustained
bulk writes through the HTTP endpoint over SMB have corrupted the listings table before; the endpoint is
meant for incremental updates of a few hundred rows.

The hosted marketplace stays current from inside the platform process - no CI job or cron is involved.
`OSR_PLATFORM_MARKETPLACE_REFRESH_S` is the interval (default `86400` = **daily**; `0` = only from the
console) and the process that owns the marketplace runs every source in
`OSR_PLATFORM_MARKETPLACE_REFRESH_SOURCES` (default: every source -
`skills.sh,mcp-registry,models,catalogue-stacks,awesome-prompts,fabric-patterns,github,github-agents,github-instructions,github-cursor-rules,github-prompts,github-chatmodes,github-custom-agents,github-claude-agents,github-claude-commands,github-cursor-commands,github-windsurf-workflows,github-codex-prompts,github-a2a,github-stacks`;
`OSR_PLATFORM_MARKETPLACE_REFRESH_LIMIT` new rows per source per run, default 2000) into
`<data dir>/harvest/`, publishes the listings the catalogue lacks in batches of 100, and re-fetches the
oldest cached rows older than `OSR_PLATFORM_MARKETPLACE_STALE_DAYS` (default 7; at most
`OSR_PLATFORM_MARKETPLACE_STALE_LIMIT` = 500 per source per run) so listings whose upstream file changed are
republished with the new text, version and endpoints. After harvesting, each cache file is compacted to the
newest line per key (re-fetched rows are appended, so the files would otherwise grow without bound), and the
publish step reads bodies back from the cache one batch at a time - a catalogue of a hundred thousand rows
costs the process tens of megabytes, not the size of the caches. Every run publishes what the caches already
hold *before* it harvests and again after each source, so a run interrupted by a deploy or an eviction loses
nothing: the next run puts out
the rows it had fetched and carries on. The first run after a (re)start waits five minutes, or until the
interval has passed since the last run finished; a run that started and never finished is resumed after five
minutes, then three times longer for every further interruption in a row, capped at **one hour** (a crash loop
backs off to hourly attempts; a deploy costs five minutes, and a service deployed several times a day still
completes its harvest). When the marketplace runs on several replicas, one of them takes a Redis lease
(`osr:marketplace:leader`) per run and the others skip it, so the shared cache files are written by one process
at a time. The lease lives ten minutes and the leader renews it while it works and releases it when done, so a
leader killed mid-run (a deploy, an eviction) frees the slot within ten minutes; a replica that found the lease
taken records the skip (`refresh.last_skipped` in the status) and looks again after fifteen minutes instead of
waiting a whole interval. The console's *Refresh now* bypasses the lease. Sources that need GitHub code search are skipped -
and reported as such - until `OSR_PLATFORM_GITHUB_TOKEN` is set (on Azure: the `OSR_PLATFORM_GITHUB_TOKEN`
deployment input becomes the `github-token` secret of every API app; a token with the `public_repo` scope is
enough). `GET /api/v1/admin/registry/import`
reports the schedule, the sources, the last run (new, updated and invalid rows per source) and the next run
time under `refresh`; `POST /api/v1/admin/registry/import/refresh` runs it now.
From a machine, `publish --url ... --only-new` does the same: it asks
`POST /api/v1/admin/registry/import/check {"slugs": [...]}` which slugs already exist and sends only the rest.

## For operators

| Setting | Default | Effect |
|---|---|---|
| `OSR_PLATFORM_REGISTRY_AUTO_PUBLISH` | `true` | Free listings go live on submit; `false` sends everything to review. |
| `OSR_PLATFORM_REGISTRY_SEED` | `true` | Seed built-in skills, templates, personas and prompts at startup. |

Moderation uses the admin token (`X-Admin-Token`):

```
GET  /api/v1/admin/registry?status=review
POST /api/v1/admin/registry/{slug}/approve      {"note": "..."}
POST /api/v1/admin/registry/{slug}/reject       {"note": "why"}
POST /api/v1/admin/registry/{slug}/flags        {"featured": true, "verified": true}
POST /api/v1/admin/registry/{slug}/unpublish    {"note": "..."}
GET  /api/v1/admin/registry/import              published listings per source
POST /api/v1/admin/registry/import              {"items": [...], "refresh": false}  (up to 500 harvested rows)
POST /api/v1/admin/registry/import/check        {"slugs": [...]} -> {"existing": [...]}  (up to 2000 slugs)
POST /api/v1/admin/registry/import/refresh      run the scheduled harvest now (202; 409 while running)
```

Paid purchases need billing to be configured (Stripe keys); without it `POST .../install` on a paid
listing answers `402`.

## Security notes

- Manifests are validated against the OCM / stack schemas and size-limited; descriptions are
  rendered without raw HTML, scripts or images and external links carry `rel="nofollow"`.
- Installing never runs anything: it returns a document. Review what a template or agent points at
  (endpoints, tools) before applying it, the same way you would review a Terraform module.
- Slugs are stable; a new version never changes the slug, so pin `@version` in production stacks.

## Related

* [Platform guide](https://osr-web.kindrock-caaab1a2.centralindia.azurecontainerapps.io/docs/PLATFORM.md) - authentication, routing, plans, organizations and the dashboard.
* [REST API reference](/docs/api/marketplace) - the *Marketplace* group lists every endpoint.
* [User guide](https://osr-web.kindrock-caaab1a2.centralindia.azurecontainerapps.io/docs/GUIDE.md) - stack files, `osr stack` and the Open Capability Manifest.
