engine9 plugin market

publishing · index.json · schema

Publishing plugins to market.engine9.ai

For teams whose registration flow publishes plugin catalog entries: engine9 (Engine9Workers.registerPluginCatalog), Frakture (BotFactory.register), and any other publisher.

market.engine9.ai keeps one catalog entry per plugin version and serves it publicly. Secrets tools, task callers and settings screens read the entry instead of your code. Most importantly, market publishes each plugin's credentials definition, the URL that secrets.engine9.ai uses to know which fields a connection needs and which are secret.

The entry format is core's standard, not market's: docs/plugin-catalog.md in @engine9/core 1.20.1 or later.

What you get #

URL
https://market.engine9.ai/plugins/<path>.jsonYour newest entry
https://market.engine9.ai/plugins/<path>@<version>.jsonThe entry for one version. Never changes
https://market.engine9.ai/definitions/<path>.jsonThe credentials definition. Use it as definition_source in secrets
https://market.engine9.ai/definitions/<path>@<version>.jsonThe definition as of one version
https://market.engine9.ai/plugins/<path>A page for people

<path> is the plugin path, the same path install uses, written as is:

https://market.engine9.ai/definitions/@frakture-com/channelbots/ActBlueBot.json
https://market.engine9.ai/definitions/@engine9/plugins/e9email.json

The definition is made from the entry's credentials with core's pluginCredentialsDefinition. You never write it yourself. A plugin with no credentials has no definition (404).

1. Get a publisher key #

engine9 sends you an invite link (https://market.engine9.ai/invite/…). Open it in a browser and press Create my key. Your key (e9mkt_…) is shown once, on that page; engine9 never sees it and keeps no copy. The link works once and expires after 7 days.

Each key may publish only its publisher's packages, for example:

PublisherKey may publish
engine9@engine9/*
Frakture@frakture-com/*

Keep the key where your deploys keep secrets (secrets.engine9.ai or your CI's secret store) and give it to the registration flow as E9MARKET_KEY. Never commit it or log it. If it is lost or leaks, ask engine9 for a new invite; your old key keeps working until engine9 revokes it, so you can switch without a gap.

Check it:

curl -s -H "Authorization: Bearer $E9MARKET_KEY" https://market.engine9.ai/v1/whoami
# { "name": "frakture", "packages": ["@frakture-com/*"] }

2. Build entries #

Your registration flow already builds entries in the standard shape (core's buildPluginCatalogEntry, or your builder that writes the same thing). Before publishing, each entry needs:

Check before you publish, locally or against market (no key needed for this):

npx e9core catalog validate                      # in a package
curl -s -X POST -H 'Content-Type: application/json' --data @entry.json https://market.engine9.ai/v1/validate

3. Publish #

Send all of a release's entries in one request: an array of entries, or a bundle { "<path>": entry }.

// At the end of registration, after the entries are built.
const res = await fetch('https://market.engine9.ai/v1/plugins', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${process.env.E9MARKET_KEY}` },
  body: JSON.stringify(entries) // [entry, …], up to 500 per request
});
const { published, unchanged, failed, results } = await res.json();
for (const r of results) if (r.http_status >= 400) console.error(`market: ${r.path}: ${r.error}`, r.errors || r.message);

Or one entry at a time: PUT https://market.engine9.ai/v1/plugins/<path> with the entry as the body.

From a shell, with entry files or bundles (e9core catalog build output, engine9-plugins.catalog.json, frakture-bots.catalog.json):

E9MARKET_KEY=e9mkt_… node scripts/publish.mjs catalog/       # in the engine9-ai/market repository

Results #

Each entry in results has http_status, and status, url, version_url, definition_url and warnings when it worked. A batch answers 200 when every entry worked and 207 when any failed.

http_statusMeaningWhat to do
201 publishedNew version stored. newest: true when it is now the newestNothing
200 unchangedThis version is already published with the same contentNothing. Registering again is safe
409 version_existsThis version is already published with different contentBump the package version. A published version never changes
422 invalid_entryThe entry does not meet the standard. errors lists whyFix the builder or the plugin's metadata
403 forbiddenThe key may not publish this packageCheck package.name, or ask for the key's packages to change
401 unauthorizedMissing, wrong or revoked keyCheck E9MARKET_KEY

Rules that matter for a registration flow #

Using the definition in secrets #

const { SecretsClient } = require('@engine9/secrets-client');
const secrets = new SecretsClient();
const definition_source = 'https://market.engine9.ai/definitions/@frakture-com/channelbots/ActBlueBot.json';

await secrets.request('acme.actblue', { definition_source, message: 'Connect ActBlue' }); // a link for a person
await secrets.set('acme.actblue', value, { definition_source });                          // or store directly

Notes for engine9 #

Notes for Frakture #

Questions #

Ask in the engine9 team channel. The entry standard is in @engine9/core; changes to it go there, not to market.

Markdown