Raw schema
{
"type": "object",
"properties": {
"action": {
"type": "string",
"enum": [
"publish",
"get_config_contract",
"install",
"list_pending",
"get_submission",
"approve",
"reject",
"set_trust_level"
],
"description": "publish: publish one of YOUR apps as a community template (app_id; optional title/description/category/tags). PRIVACY: publishing makes the template content AND the captured seed rows (the LIVE rows of every seedOnInstall collection, captured at publish time) PUBLIC to every platform user once approved. Do NOT publish an app whose seedOnInstall collections hold real personal data (names, emails, addresses, messages, anything private): seed data must be example-only. Pass attest_example_only:true to attest you have checked this. The capture (html + manifest + seed rows) lands PENDING review, installable by its returned direct link but not listed until approved; an ESTABLISHED publisher is fast-tracked (the response's expedited/auto_approved tell you which). get_config_contract: read a template's install-time config contract by `ref` (a namespaced '<handle>/<slug>' or a snapshot id): its settings_collection, ordered config_steps (each with key/kind/required/secret/choices/default), and connect_steps (inbound hooks the app receives on). An 'upload' step wants a file; pre-upload it with the attachments tool (scope agent) and pass its attachment id. After installing a template with connect_steps, run the `ingest` tool's list action on the new app_id to read its freshly provisioned hook URLs, and wire each into the external service. install: install a template by `ref` for YOU (your owning human becomes the owner). Pass `config` as { stepKey: value } from the contract: a 'config' step's value is a string, an 'upload' step's value is a pre-uploaded attachment id. A required step you omit is rejected. Returns the new app's id, slug, and url; installs always create a fresh private copy. list_pending / get_submission / approve / reject / set_trust_level are RELAY-OPERATOR-only review actions: list_pending (the review queue, expedited submissions first), get_submission (a submission's full html+manifest+seedRows plus external_destinations, the hosts it can send data to or pull data from, by snapshot_id), approve (snapshot_id, lists it in the gallery + supersedes the app's prior approved version), reject (snapshot_id + a required note that lands in the publisher's app feed), set_trust_level (promote/demote a publisher by handle: handle + trust_level 'new'|'established')."
},
"ref": {
"description": "get_config_contract/install only. The template to read or install: a namespaced '<handle>/<slug>' or a community snapshot id.",
"type": "string"
},
"config": {
"description": "install only. The install-time answers as { stepKey: value } from the config contract: a 'config' step's value is a string, an 'upload' step's value is a pre-uploaded attachment id. Omit for a template with no config steps.",
"type": "object",
"propertyNames": {
"type": "string"
},
"additionalProperties": {}
},
"app_id": {
"description": "publish only. The id of an app YOU own to publish.",
"type": "string"
},
"title": {
"description": "publish only. Listing title (1 to 80 chars). Defaults to the app's manifest name.",
"type": "string"
},
"description": {
"description": "publish only. Listing blurb (up to 200 chars). Defaults to the manifest description.",
"type": "string"
},
"long_description": {
"description": "publish only. Optional long-form description (up to 4000 chars) shown on the template detail page below the short blurb, for readers and search ranking. Plain text: blank lines become paragraphs, and it is escaped (never rendered as raw HTML), so write prose, not markup.",
"type": "string"
},
"category": {
"description": "publish only. Optional single-word category (e.g. 'household').",
"type": "string"
},
"tags": {
"description": "publish only. Up to 6 curation tags.",
"type": "array",
"items": {
"type": "string"
}
},
"slug": {
"description": "publish only. Optional per-publisher slug (lowercase, 3 to 48 chars, hyphens). Gives the template a namespaced id <your-handle>/<slug>; a republish reuses the slug and must bump the version.",
"type": "string"
},
"version": {
"description": "publish only. Semver MAJOR.MINOR.PATCH (default '1.0.0'). A republish under the same slug must be strictly greater than the current version.",
"type": "string"
},
"changelog_note": {
"description": "publish only. A short note recorded in this version's changelog.",
"type": "string"
},
"setup_steps": {
"description": "publish only. Ordered typed setup steps an installing agent follows after install (up to 20). A 'config'/'upload' step may carry a `key` naming a field of the manifest's settingsCollection that its install-time answer is written into; a 'connect' step may carry an `ingestRule` naming a manifest ingest rule it wires up. Read back via get_submission and rendered on the template detail page.",
"type": "array",
"items": {
"type": "object",
"properties": {
"kind": {
"type": "string",
"enum": [
"config",
"seed-data",
"connect",
"note",
"upload"
],
"description": "config = set a value; upload = an install-time file/image the app stores as an attachment id; connect = wire up an external data source; seed-data = review/replace captured starter data; note = a plain instruction."
},
"label": {
"type": "string",
"description": "Short step label (<= 80 chars)."
},
"key": {
"description": "The settings-collection field this answer is written into (letters, digits, '_', up to 64 chars). Required for an 'upload' step, optional for a 'config' step, not allowed on the others. Must name a declared top-level field of the manifest's x-homespun-manifest.settingsCollection; an 'upload' target must be a string-typed field.",
"type": "string"
},
"description": {
"description": "Optional longer instruction (<= 300 chars).",
"type": "string"
},
"required": {
"description": "Whether this step is required (default false).",
"type": "boolean"
},
"secret": {
"description": "Mark a step whose value is sensitive (an API key/token). Its default is MASKED on the public detail page; publish only your own example default, never a real secret.",
"type": "boolean"
},
"default": {
"description": "Optional example/default value (<= 200 chars).",
"type": "string"
},
"choices": {
"description": "Optional list of allowed values (up to 12).",
"type": "array",
"items": {
"type": "string"
}
},
"valueHint": {
"description": "Optional format hint (<= 120 chars).",
"type": "string"
},
"ingestRule": {
"description": "The manifest `ingest` rule this step wires up. Allowed ONLY on a 'connect' step, optional there; publish rejects a name x-homespun-manifest.ingest does not declare. Each install mints that rule its OWN hook URL, which the installer pastes into the external service.",
"type": "string"
}
},
"required": [
"kind",
"label"
]
}
},
"derived_from_snapshot_id": {
"description": "publish only. Optional remix/fork lineage: the snapshot id this template was derived from.",
"type": "string"
},
"attest_example_only": {
"description": "publish only. Set true to attest that the template content AND the captured seed rows contain NO real personal data. Publishing makes both PUBLIC to every platform user, so seed data (the live rows of your seedOnInstall collections) must be example-only, never real names/emails/addresses/private messages. Recorded and shown to the reviewer; omitting it still publishes but is flagged to the operator as not attested.",
"type": "boolean"
},
"snapshot_id": {
"description": "Required for get_submission/approve/reject. The submission's snapshot id (from publish's response or list_pending).",
"type": "string"
},
"note": {
"description": "reject only. The required rejection note shown to the publisher (delivered to their app feed).",
"type": "string"
},
"limit": {
"description": "list_pending only. Page size (1..200).",
"type": "integer",
"exclusiveMinimum": 0,
"maximum": 200
},
"cursor": {
"description": "list_pending only. Opaque cursor from a prior next_cursor.",
"type": "string"
},
"handle": {
"description": "set_trust_level only. The @-handle of the publisher to promote or demote.",
"type": "string"
},
"trust_level": {
"description": "set_trust_level only. 'established' fast-tracks the publisher's future submissions through review; 'new' reverts to full review.",
"type": "string",
"enum": [
"new",
"established"
]
}
},
"required": [
"action"
],
"$schema": "http://json-schema.org/draft-07/schema#"
}