{
  "openapi": "3.1.0",
  "info": {
    "title": "RoutineHub API",
    "version": "1.0.0",
    "summary": "Official HTTP API for RoutineHub — shortcuts and the Claude registry (prompts, skills, plugins).",
    "description": "The official RoutineHub v1 API.\n\n## Authentication\nMost endpoints are authenticated with an **API key carried as a path segment**, e.g. `GET /api/v1/<api_key>/shortcuts`. Generate or rotate your key on the RoutineHub **Settings → API** page; generating a new key invalidates the old one. Store the key on your own machine, never in a shared repo. A few endpoints are public (the latest-version lookup) or use a separate HubSign key (`/sign-shortcut`).\n\n## Response envelope\nEvery JSON response uses a `result` field: `{\"result\": \"success\", ...}` or `{\"result\": \"error\", \"message\": \"...\"}`. Form-validation errors also include an `errors` object keyed by field name.\n\n## Status codes\nThe newer **registry** endpoints (skills/prompts/plugins) use standard HTTP status codes: `201` created, `400` invalid, `401` bad key, `404` not found / not yours, `409` duplicate content, `429` rate-limited. The older **shortcut** endpoints are legacy: most of them return **HTTP 200 even on failure** — always check the `result` field rather than the status code for those.\n\n## Rate limits\nRegistry publishing is capped per day per account: skills 5, prompts 10, plugins 5, and 20 new versions/day. Exceeding a cap returns `429`.",
    "contact": {
      "name": "RoutineHub",
      "url": "https://routinehub.co"
    }
  },
  "servers": [
    {
      "url": "https://routinehub.co/api/v1",
      "description": "Production"
    }
  ],
  "tags": [
    {
      "name": "Shortcuts",
      "description": "Publish and retrieve iOS Shortcuts."
    },
    {
      "name": "Skills",
      "description": "Publish Claude Skills to the registry."
    },
    {
      "name": "Prompts",
      "description": "Publish Claude Prompts (slash commands) to the registry."
    },
    {
      "name": "Plugins",
      "description": "Publish Claude Plugins (bundles of skills/prompts) to the registry."
    }
  ],
  "paths": {
    "/{api_key}/shortcuts": {
      "get": {
        "tags": [
          "Shortcuts"
        ],
        "summary": "List your shortcuts",
        "description": "Returns every shortcut owned by the account that the API key belongs to, keyed by shortcut name.",
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiKeyPath"
          }
        ],
        "responses": {
          "200": {
            "description": "Map of shortcut name to id + published flag. NOTE: a bad key also returns 200 with `result: error`.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "result": {
                      "type": "string",
                      "example": "success"
                    },
                    "shortcuts": {
                      "type": "object",
                      "additionalProperties": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "integer",
                            "example": 7388
                          },
                          "published": {
                            "type": "string",
                            "enum": [
                              "true",
                              "false"
                            ],
                            "description": "String literal, not a boolean."
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/{api_key}/shortcuts/create": {
      "post": {
        "tags": [
          "Shortcuts"
        ],
        "summary": "Create a shortcut (with its first version)",
        "description": "Creates a new shortcut AND its first version in one call, from an iCloud share link you already have (Share > Copy iCloud Link in the Shortcuts app — minting the link needs a real Apple device). Draft by default; pass published=true to go live at once. Optional price_usd + payout put it on sale in the same call; a rejected price returns 400 and creates nothing. Submitted as form fields, not JSON.",
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiKeyPath"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "required": [
                  "name",
                  "brief",
                  "description",
                  "categories",
                  "link"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "Shortcut title."
                  },
                  "brief": {
                    "type": "string",
                    "maxLength": 100,
                    "description": "One-line summary."
                  },
                  "description": {
                    "type": "string",
                    "description": "Full listing text (Markdown)."
                  },
                  "categories": {
                    "type": "array",
                    "items": {
                      "type": "integer"
                    },
                    "description": "1-2 category ids (repeat the field)."
                  },
                  "link": {
                    "type": "string",
                    "description": "iCloud share link for the first version.",
                    "pattern": "^https://www\\.icloud\\.com/shortcuts/[a-z0-9]{32}$"
                  },
                  "version": {
                    "type": "string",
                    "maxLength": 25,
                    "description": "First version label. Default \"1.0\"."
                  },
                  "changes": {
                    "type": "string",
                    "description": "Changelog text. Default \"Initial release.\"."
                  },
                  "published": {
                    "type": "boolean",
                    "description": "true to publish immediately. Default false (draft)."
                  },
                  "price_usd": {
                    "type": "string",
                    "description": "Optional: sell it from day one, e.g. \"4.99\" (min $0.50, max $100000, 2 decimals)."
                  },
                  "payout": {
                    "type": "string",
                    "enum": [
                      "stripe",
                      "crypto_usdc_base"
                    ],
                    "description": "Payout rail when price_usd is set; required only when both rails are onboarded."
                  },
                  "payment_type": {
                    "type": "string",
                    "enum": [
                      "one_time",
                      "recurring"
                    ],
                    "description": "One-time purchase (default) or 30-day recurring subscription."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created. Returns the shortcut's id (use it for every other shortcut endpoint), url, latest_version, and the sale state.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "result": {
                      "type": "string"
                    },
                    "id": {
                      "type": "integer"
                    },
                    "name": {
                      "type": "string"
                    },
                    "published": {
                      "type": "boolean"
                    },
                    "url": {
                      "type": "string"
                    },
                    "latest_version": {
                      "type": "string"
                    },
                    "price_usd": {
                      "type": "string",
                      "nullable": true
                    },
                    "payout": {
                      "type": "string",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid link (not iCloud / already in use), invalid metadata (an errors object per field), or a rejected price. Nothing is created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MessageEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MessageEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "The account is restricted from publishing.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MessageEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/{api_key}/shortcuts/{id}/price": {
      "post": {
        "tags": [
          "Shortcuts"
        ],
        "summary": "Set or clear a shortcut's price",
        "description": "Change ONLY the price of a shortcut you own — both payout rails (card via Stripe, USDC on Base), one-time or recurring. The shortcut-side mirror of the registry price endpoint: touches pricing and nothing else, so it can never blank the listing. Send price_usd \"0\" to make it free again.",
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiKeyPath"
          },
          {
            "$ref": "#/components/parameters/ShortcutId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "required": [
                  "price_usd"
                ],
                "properties": {
                  "price_usd": {
                    "type": "string",
                    "description": "Price as a plain 2-decimal string, e.g. \"1.99\" (minimum $0.50, maximum $100000). Amounts like \"1.999\" are rejected with 400. Send \"0\" to make the shortcut free again."
                  },
                  "payout": {
                    "type": "string",
                    "enum": [
                      "stripe",
                      "crypto_usdc_base"
                    ],
                    "description": "Required when price_usd > 0 and both payout rails are onboarded; otherwise the onboarded rail is used."
                  },
                  "payment_type": {
                    "type": "string",
                    "enum": [
                      "one_time",
                      "recurring"
                    ],
                    "description": "One-time purchase (default) or 30-day recurring subscription."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated sale state (price_usd and payout are null after clearing).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "result": {
                      "type": "string"
                    },
                    "id": {
                      "type": "integer"
                    },
                    "name": {
                      "type": "string"
                    },
                    "published": {
                      "type": "boolean"
                    },
                    "url": {
                      "type": "string"
                    },
                    "latest_version": {
                      "type": "string",
                      "nullable": true
                    },
                    "price_usd": {
                      "type": "string",
                      "nullable": true
                    },
                    "payout": {
                      "type": "string",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing/invalid price_usd, unknown payment_type, or the chosen payout rail is not onboarded (the message names the rail and where to set it up).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MessageEnvelope"
                }
              }
            }
          },
          "404": {
            "description": "No shortcut with that ID under your account.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MessageEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/{api_key}/shortcuts/{id}/versions/create": {
      "post": {
        "tags": [
          "Shortcuts"
        ],
        "summary": "Create a new shortcut version",
        "description": "Adds a new version to a shortcut you own. Submitted as form fields, not JSON.",
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiKeyPath"
          },
          {
            "$ref": "#/components/parameters/ShortcutId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "required": [
                  "version",
                  "link",
                  "changes"
                ],
                "properties": {
                  "version": {
                    "type": "string",
                    "maxLength": 10,
                    "description": "Version label, e.g. 1.2. Must not duplicate a version already in review/approved."
                  },
                  "link": {
                    "type": "string",
                    "description": "iCloud share link.",
                    "pattern": "^https://www\\.icloud\\.com/shortcuts/[a-z0-9]{32}$"
                  },
                  "changes": {
                    "type": "string",
                    "description": "Changelog text. Emojis are rejected."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "`result: success` with a message on success, or `result: error` with a reason (legacy: errors are also 200).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MessageEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/{api_key}/shortcuts/{id}/publish": {
      "post": {
        "tags": [
          "Shortcuts"
        ],
        "summary": "Publish a shortcut",
        "description": "Publishes a shortcut you own. It must already have at least one version.",
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiKeyPath"
          },
          {
            "$ref": "#/components/parameters/ShortcutId"
          }
        ],
        "responses": {
          "200": {
            "description": "`result: success` on success; `result: error` (still 200) if it has no version or is already published.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MessageEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/{api_key}/shortcuts/{id}/unpublish": {
      "post": {
        "tags": [
          "Shortcuts"
        ],
        "summary": "Unpublish a shortcut",
        "description": "Unpublishes a shortcut you own.",
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiKeyPath"
          },
          {
            "$ref": "#/components/parameters/ShortcutId"
          }
        ],
        "responses": {
          "200": {
            "description": "`result: success` on success; `result: error` (still 200) if it was already unpublished.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MessageEnvelope"
                }
              }
            }
          }
        }
      }
    },
    "/shortcuts/{id}/versions/latest": {
      "get": {
        "tags": [
          "Shortcuts"
        ],
        "summary": "Get the latest version of a published shortcut (public)",
        "description": "Public — no API key required. Returns the latest version metadata of a published shortcut. Sends permissive CORS headers so it can be called from the browser. Updater apps: pass a valid API key (the `api_key` query parameter or `RoutineHub-Api-Key` header — generate one under Settings → API) and `URL` will be a short-lived signed download link that redirects straight to the shortcut file. Without a key, `URL` is the shortcut's RoutineHub page.",
        "parameters": [
          {
            "$ref": "#/components/parameters/ShortcutId"
          },
          {
            "name": "api_key",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Optional. A valid RoutineHub API key (Settings → API). Authenticated callers get a working, short-lived signed download link in `URL` instead of the shortcut page. Fetch a fresh link per update check — links expire about an hour after minting."
          }
        ],
        "responses": {
          "200": {
            "description": "Latest version metadata.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "result": {
                      "type": "string",
                      "example": "success"
                    },
                    "id": {
                      "type": "integer",
                      "example": 7388
                    },
                    "Version": {
                      "type": "string",
                      "example": "1.2"
                    },
                    "URL": {
                      "type": "string",
                      "description": "Where to send the user. For API-key-authenticated callers and approved updater partners: a short-lived signed download link that redirects to the shortcut file. Otherwise: the shortcut's RoutineHub page URL. Paid shortcuts always return the page URL so the purchase flow runs."
                    },
                    "Notes": {
                      "type": "string",
                      "description": "Changelog for the latest version."
                    },
                    "Release": {
                      "type": "string",
                      "example": "June 27, 2026"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/sign-shortcut": {
      "post": {
        "tags": [
          "Shortcuts"
        ],
        "summary": "Sign a .shortcut file (HubSign)",
        "description": "Signs an unsigned `.shortcut` file via HubSign and streams back the signed file. Requires a HubSign API key (separate from the main key) and an active membership with HubSign access, unless called by an approved partner client.",
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "shortcut_name",
                  "shortcut_file"
                ],
                "properties": {
                  "shortcut_name": {
                    "type": "string",
                    "description": "Name for the signed file (becomes <name>.shortcut)."
                  },
                  "shortcut_file": {
                    "type": "string",
                    "format": "binary",
                    "description": "The unsigned .shortcut file."
                  },
                  "api_key": {
                    "type": "string",
                    "description": "HubSign API key. Required for non-partner clients."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The signed .shortcut file.",
            "content": {
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "402": {
            "description": "HubSign access requires an active RoutineHub membership.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/{api_key}/registry/skills": {
      "get": {
        "tags": [
          "Skills"
        ],
        "summary": "List your skills",
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiKeyPath"
          }
        ],
        "responses": {
          "200": {
            "description": "Your skills.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "result": {
                      "type": "string",
                      "example": "success"
                    },
                    "skills": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Skill"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error"
          }
        }
      },
      "post": {
        "tags": [
          "Skills"
        ],
        "summary": "Publish a new skill",
        "description": "Creates a skill and its first version with one plain form POST — paste the SKILL.md as the `skill_md` field (works from a phone or curl, no multipart needed) or send a multi-file bundle as repeated `files` parts. Free by default; add `price_usd` to sell it. Runs the same validation/ingest pipeline as the website.",
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiKeyPath"
          }
        ],
        "requestBody": {
          "required": true,
          "description": "Provide the content either way (or both): `skill_md` — the SKILL.md text pasted as a plain form field (the simplest path: works from a phone, the Shortcuts app, or curl with no multipart) — and/or repeated `files` parts, one per bundle file, each part's filename being its bundle-relative path. The bundle must contain exactly one root SKILL.md with `name` + `description` YAML frontmatter. Limits: 25 files, 256 KB per file, 2 MB total; file types .md, .skill and .plugin only.",
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "name",
                  "brief",
                  "version"
                ],
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "slug": {
                    "type": "string",
                    "description": "Optional URL permalink; generated from the name when blank."
                  },
                  "brief": {
                    "type": "string",
                    "description": "One-line summary (max 200 chars)."
                  },
                  "description": {
                    "type": "string",
                    "description": "Optional listing page text (markdown)."
                  },
                  "version": {
                    "type": "string",
                    "description": "First version label, e.g. \"1.0\"."
                  },
                  "changes": {
                    "type": "string",
                    "description": "Optional release notes."
                  },
                  "claude_surface": {
                    "type": "string",
                    "default": "all",
                    "description": "Optional; defaults to `all`."
                  },
                  "skill_md": {
                    "type": "string",
                    "description": "The SKILL.md content pasted as text. Alternative to `files`."
                  },
                  "files": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "binary"
                    },
                    "description": "Repeated `files` parts, one per bundle file; each filename is the bundle-relative path."
                  },
                  "price_usd": {
                    "type": "string",
                    "description": "Optional: sell this skill. A plain 2 decimal amount as a string, e.g. \"1.99\" — minimum $0.50, at most 2 decimal places (amounts like \"1.999\" are rejected with 400; values are handled as exact decimals, never floats). Send \"0\" to make it free (community). Omit to leave unchanged. Requires completed seller onboarding."
                  },
                  "payout": {
                    "type": "string",
                    "enum": [
                      "stripe",
                      "crypto_usdc_base"
                    ],
                    "description": "Payout rail when price_usd > 0. Required only if both rails are onboarded."
                  }
                }
              }
            },
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "required": [
                  "name",
                  "brief",
                  "version"
                ],
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "slug": {
                    "type": "string",
                    "description": "Optional URL permalink; generated from the name when blank."
                  },
                  "brief": {
                    "type": "string",
                    "description": "One-line summary (max 200 chars)."
                  },
                  "description": {
                    "type": "string",
                    "description": "Optional listing page text (markdown)."
                  },
                  "version": {
                    "type": "string",
                    "description": "First version label, e.g. \"1.0\"."
                  },
                  "changes": {
                    "type": "string",
                    "description": "Optional release notes."
                  },
                  "claude_surface": {
                    "type": "string",
                    "default": "all",
                    "description": "Optional; defaults to `all`."
                  },
                  "skill_md": {
                    "type": "string",
                    "description": "The SKILL.md content pasted as text. Alternative to `files`."
                  },
                  "price_usd": {
                    "type": "string",
                    "description": "Optional: sell this skill. A plain 2 decimal amount as a string, e.g. \"1.99\" — minimum $0.50, at most 2 decimal places (amounts like \"1.999\" are rejected with 400; values are handled as exact decimals, never floats). Send \"0\" to make it free (community). Omit to leave unchanged. Requires completed seller onboarding."
                  },
                  "payout": {
                    "type": "string",
                    "enum": [
                      "stripe",
                      "crypto_usdc_base"
                    ],
                    "description": "Payout rail when price_usd > 0. Required only if both rails are onboarded."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Skill created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Skill"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/{api_key}/registry/skills/{slug}": {
      "get": {
        "tags": [
          "Skills"
        ],
        "summary": "Get a skill",
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiKeyPath"
          },
          {
            "$ref": "#/components/parameters/Slug"
          }
        ],
        "responses": {
          "200": {
            "description": "The skill.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Skill"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          }
        }
      },
      "post": {
        "tags": [
          "Skills"
        ],
        "summary": "Update skill metadata",
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiKeyPath"
          },
          {
            "$ref": "#/components/parameters/Slug"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "slug": {
                    "type": "string"
                  },
                  "brief": {
                    "type": "string"
                  },
                  "published": {
                    "type": "string",
                    "enum": [
                      "on",
                      "off"
                    ]
                  },
                  "price_usd": {
                    "type": "string",
                    "description": "Set/update price, e.g. \"1.99\" (min $0.50). Send \"0\" to make free. Omit to leave unchanged. Requires completed onboarding."
                  },
                  "payout": {
                    "type": "string",
                    "enum": [
                      "stripe",
                      "crypto_usdc_base"
                    ],
                    "description": "Required when price_usd > 0 and both rails are onboarded."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Skill"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          }
        }
      },
      "delete": {
        "tags": [
          "Skills"
        ],
        "summary": "Delete a skill",
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiKeyPath"
          },
          {
            "$ref": "#/components/parameters/Slug"
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Deleted"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/{api_key}/registry/skills/{slug}/versions": {
      "get": {
        "tags": [
          "Skills"
        ],
        "summary": "List skill versions",
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiKeyPath"
          },
          {
            "$ref": "#/components/parameters/Slug"
          }
        ],
        "responses": {
          "200": {
            "description": "Versions.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VersionList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          }
        }
      },
      "post": {
        "tags": [
          "Skills"
        ],
        "summary": "Publish a new skill version",
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiKeyPath"
          },
          {
            "$ref": "#/components/parameters/Slug"
          }
        ],
        "requestBody": {
          "required": true,
          "description": "Provide the content either way (or both): `skill_md` — the SKILL.md text pasted as a plain form field (the simplest path: works from a phone, the Shortcuts app, or curl with no multipart) — and/or repeated `files` parts, one per bundle file, each part's filename being its bundle-relative path. The bundle must contain exactly one root SKILL.md with `name` + `description` YAML frontmatter. Limits: 25 files, 256 KB per file, 2 MB total; file types .md, .skill and .plugin only.",
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "version"
                ],
                "properties": {
                  "version": {
                    "type": "string",
                    "description": "New version label, e.g. \"1.1\"."
                  },
                  "changes": {
                    "type": "string",
                    "description": "Optional release notes."
                  },
                  "claude_surface": {
                    "type": "string",
                    "default": "all",
                    "description": "Optional; defaults to `all`."
                  },
                  "skill_md": {
                    "type": "string",
                    "description": "The SKILL.md content pasted as text. Alternative to `files`."
                  },
                  "files": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "binary"
                    },
                    "description": "Repeated `files` parts, one per bundle file; each filename is the bundle-relative path."
                  }
                }
              }
            },
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "required": [
                  "version"
                ],
                "properties": {
                  "version": {
                    "type": "string",
                    "description": "New version label, e.g. \"1.1\"."
                  },
                  "changes": {
                    "type": "string",
                    "description": "Optional release notes."
                  },
                  "claude_surface": {
                    "type": "string",
                    "default": "all",
                    "description": "Optional; defaults to `all`."
                  },
                  "skill_md": {
                    "type": "string",
                    "description": "The SKILL.md content pasted as text. Alternative to `files`."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Version created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VersionCreated"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        },
        "description": "Publishes a new immutable version of a skill you own. Same content fields as publish: paste `skill_md` or send repeated `files` parts."
      }
    },
    "/{api_key}/registry/prompts": {
      "get": {
        "tags": [
          "Prompts"
        ],
        "summary": "List your prompts",
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiKeyPath"
          }
        ],
        "responses": {
          "200": {
            "description": "Your prompts.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "result": {
                      "type": "string",
                      "example": "success"
                    },
                    "prompts": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Prompt"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error"
          }
        }
      },
      "post": {
        "tags": [
          "Prompts"
        ],
        "summary": "Publish a new prompt",
        "description": "Creates a prompt and its first version (the body is stored as the version). Form-encoded.",
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiKeyPath"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "required": [
                  "command",
                  "name",
                  "brief",
                  "kind"
                ],
                "properties": {
                  "command": {
                    "type": "string",
                    "description": "Slash command, e.g. /summarize."
                  },
                  "name": {
                    "type": "string"
                  },
                  "brief": {
                    "type": "string"
                  },
                  "kind": {
                    "type": "string"
                  },
                  "version": {
                    "type": "string"
                  },
                  "changes": {
                    "type": "string"
                  },
                  "body": {
                    "type": "string",
                    "description": "Prompt text, stored as the first version."
                  },
                  "price_usd": {
                    "type": "string",
                    "description": "Set/update price, e.g. \"1.99\" (min $0.50). Send \"0\" to make free. Omit to leave unchanged. Requires completed onboarding."
                  },
                  "payout": {
                    "type": "string",
                    "enum": [
                      "stripe",
                      "crypto_usdc_base"
                    ],
                    "description": "Required when price_usd > 0 and both rails are onboarded."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Prompt created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Prompt"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/{api_key}/registry/prompts/{slug}": {
      "get": {
        "tags": [
          "Prompts"
        ],
        "summary": "Get a prompt",
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiKeyPath"
          },
          {
            "$ref": "#/components/parameters/Slug"
          }
        ],
        "responses": {
          "200": {
            "description": "The prompt.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Prompt"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          }
        }
      },
      "post": {
        "tags": [
          "Prompts"
        ],
        "summary": "Update prompt metadata",
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiKeyPath"
          },
          {
            "$ref": "#/components/parameters/Slug"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "properties": {
                  "command": {
                    "type": "string"
                  },
                  "name": {
                    "type": "string"
                  },
                  "brief": {
                    "type": "string"
                  },
                  "kind": {
                    "type": "string"
                  },
                  "published": {
                    "type": "string",
                    "enum": [
                      "on",
                      "off"
                    ]
                  },
                  "price_usd": {
                    "type": "string",
                    "description": "Set/update price, e.g. \"1.99\" (min $0.50). Send \"0\" to make free. Omit to leave unchanged. Requires completed onboarding."
                  },
                  "payout": {
                    "type": "string",
                    "enum": [
                      "stripe",
                      "crypto_usdc_base"
                    ],
                    "description": "Required when price_usd > 0 and both rails are onboarded."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Prompt"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          }
        }
      },
      "delete": {
        "tags": [
          "Prompts"
        ],
        "summary": "Delete a prompt",
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiKeyPath"
          },
          {
            "$ref": "#/components/parameters/Slug"
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Deleted"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/{api_key}/registry/prompts/{slug}/versions": {
      "get": {
        "tags": [
          "Prompts"
        ],
        "summary": "List prompt versions",
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiKeyPath"
          },
          {
            "$ref": "#/components/parameters/Slug"
          }
        ],
        "responses": {
          "200": {
            "description": "Versions.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VersionList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          }
        }
      },
      "post": {
        "tags": [
          "Prompts"
        ],
        "summary": "Publish a new prompt version",
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiKeyPath"
          },
          {
            "$ref": "#/components/parameters/Slug"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "required": [
                  "version",
                  "changes",
                  "body"
                ],
                "properties": {
                  "version": {
                    "type": "string"
                  },
                  "changes": {
                    "type": "string"
                  },
                  "body": {
                    "type": "string",
                    "description": "New prompt text."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Version created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VersionCreated"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/{api_key}/registry/plugins": {
      "get": {
        "tags": [
          "Plugins"
        ],
        "summary": "List your plugins",
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiKeyPath"
          }
        ],
        "responses": {
          "200": {
            "description": "Your plugins.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "result": {
                      "type": "string",
                      "example": "success"
                    },
                    "plugins": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Plugin"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error"
          }
        }
      },
      "post": {
        "tags": [
          "Plugins"
        ],
        "summary": "Publish a new plugin",
        "description": "Creates a plugin and snapshots its first version. Membership (the skills/prompts it bundles) is set with comma-separated permalinks. Form-encoded.",
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiKeyPath"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "required": [
                  "namespace",
                  "role",
                  "package_name",
                  "name",
                  "brief"
                ],
                "properties": {
                  "namespace": {
                    "type": "string"
                  },
                  "role": {
                    "type": "string"
                  },
                  "package_name": {
                    "type": "string"
                  },
                  "name": {
                    "type": "string"
                  },
                  "brief": {
                    "type": "string"
                  },
                  "skills": {
                    "type": "string",
                    "description": "Comma-separated skill permalinks to bundle."
                  },
                  "prompts": {
                    "type": "string",
                    "description": "Comma-separated prompt permalinks to bundle."
                  },
                  "price_usd": {
                    "type": "string",
                    "description": "Set/update price, e.g. \"1.99\" (min $0.50). Send \"0\" to make free. Omit to leave unchanged. Requires completed onboarding."
                  },
                  "payout": {
                    "type": "string",
                    "enum": [
                      "stripe",
                      "crypto_usdc_base"
                    ],
                    "description": "Required when price_usd > 0 and both rails are onboarded."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Plugin created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Plugin"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/{api_key}/registry/plugins/{slug}": {
      "get": {
        "tags": [
          "Plugins"
        ],
        "summary": "Get a plugin",
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiKeyPath"
          },
          {
            "$ref": "#/components/parameters/Slug"
          }
        ],
        "responses": {
          "200": {
            "description": "The plugin.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Plugin"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          }
        }
      },
      "post": {
        "tags": [
          "Plugins"
        ],
        "summary": "Update plugin metadata",
        "description": "Updates metadata; optionally re-sets membership if `skills` or `prompts` are supplied.",
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiKeyPath"
          },
          {
            "$ref": "#/components/parameters/Slug"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "brief": {
                    "type": "string"
                  },
                  "role": {
                    "type": "string"
                  },
                  "skills": {
                    "type": "string"
                  },
                  "prompts": {
                    "type": "string"
                  },
                  "price_usd": {
                    "type": "string",
                    "description": "Set/update price, e.g. \"1.99\" (min $0.50). Send \"0\" to make free. Omit to leave unchanged. Requires completed onboarding."
                  },
                  "payout": {
                    "type": "string",
                    "enum": [
                      "stripe",
                      "crypto_usdc_base"
                    ],
                    "description": "Required when price_usd > 0 and both rails are onboarded."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Plugin"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          }
        }
      },
      "delete": {
        "tags": [
          "Plugins"
        ],
        "summary": "Delete a plugin",
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiKeyPath"
          },
          {
            "$ref": "#/components/parameters/Slug"
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Deleted"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/{api_key}/registry/plugins/{slug}/versions": {
      "get": {
        "tags": [
          "Plugins"
        ],
        "summary": "List plugin versions",
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiKeyPath"
          },
          {
            "$ref": "#/components/parameters/Slug"
          }
        ],
        "responses": {
          "200": {
            "description": "Versions.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VersionList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          }
        }
      },
      "post": {
        "tags": [
          "Plugins"
        ],
        "summary": "Snapshot a new plugin version",
        "description": "Snapshots the plugin's current membership as a new pinned version.",
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiKeyPath"
          },
          {
            "$ref": "#/components/parameters/Slug"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "required": [
                  "version"
                ],
                "properties": {
                  "version": {
                    "type": "string"
                  },
                  "changes": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Version created.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VersionCreated"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/{api_key}/mcp": {
      "post": {
        "operationId": "mcpEndpoint",
        "tags": [
          "Registry MCP"
        ],
        "summary": "MCP server (JSON-RPC 2.0) — agents publish, fork and update registry items",
        "description": "A stateless Model Context Protocol endpoint over streamable HTTP. Point an MCP client (Claude Code, any agent framework) at this URL with your API key in the path. Supports initialize, tools/list and tools/call with 17 tools: search_registry, get_item, install_item, diff_versions, fork_item, publish_prompt, publish_cherri, publish_shortcut, publish_skill, publish_plugin, publish_version, set_price, set_license, list_my_items, propose_update, list_proposals, decide_proposal. install_item returns the complete installable file set for anything you can access; publish_skill and publish_plugin upload whole bundles; publish_shortcut creates an Apple Shortcut listing from an iCloud share link you already have, and set_price also prices shortcuts (pass the numeric id as slug). Paid sources stay locked until purchased; forks carry permanent attribution; paid forks accrue a royalty to the original author.",
        "parameters": [
          {
            "name": "api_key",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Your RoutineHub API key (Settings → API)."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "A JSON-RPC 2.0 request (initialize, tools/list, tools/call, ping).",
                "properties": {
                  "jsonrpc": {
                    "type": "string",
                    "enum": [
                      "2.0"
                    ]
                  },
                  "id": {
                    "type": "integer"
                  },
                  "method": {
                    "type": "string"
                  },
                  "params": {
                    "type": "object"
                  }
                },
                "required": [
                  "jsonrpc",
                  "method"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "JSON-RPC response (result or error object)."
          },
          "202": {
            "description": "Notification accepted (requests without an id)."
          },
          "401": {
            "description": "Invalid API key."
          }
        }
      }
    },
    "/{api_key}/registry/{bucket}/{slug}/price": {
      "post": {
        "tags": [
          "Skills"
        ],
        "summary": "Set or clear an item's price",
        "description": "Change ONLY the price of a skill, prompt or plugin you own. Unlike the item's detail POST (which runs the full listing form, so any field you omit is blanked), this endpoint touches pricing and nothing else. Used by the `npx routinehub` CLI.",
        "parameters": [
          {
            "$ref": "#/components/parameters/ApiKeyPath"
          },
          {
            "name": "bucket",
            "in": "path",
            "required": true,
            "description": "Item type.",
            "schema": {
              "type": "string",
              "enum": [
                "skills",
                "prompts",
                "plugins"
              ]
            }
          },
          {
            "$ref": "#/components/parameters/Slug"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "required": [
                  "price_usd"
                ],
                "properties": {
                  "price_usd": {
                    "type": "string",
                    "description": "Price as a plain 2-decimal string, e.g. \"1.99\" (minimum $0.50, maximum $100000). Amounts like \"1.999\" are rejected with 400. Send \"0\" to make the item free again."
                  },
                  "payout": {
                    "type": "string",
                    "enum": [
                      "stripe",
                      "crypto_usdc_base"
                    ],
                    "description": "Required when price_usd > 0 and both payout rails are onboarded; otherwise the onboarded rail is used."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The item's new price.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "result": {
                      "type": "string",
                      "example": "success"
                    },
                    "slug": {
                      "type": "string"
                    },
                    "name": {
                      "type": "string"
                    },
                    "url": {
                      "type": "string"
                    },
                    "price_usd": {
                      "type": "string",
                      "nullable": true,
                      "description": "null when the item is free."
                    },
                    "payout": {
                      "type": "string",
                      "nullable": true,
                      "enum": [
                        "stripe",
                        "crypto_usdc_base"
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    }
  },
  "components": {
    "parameters": {
      "ApiKeyPath": {
        "name": "api_key",
        "in": "path",
        "required": true,
        "description": "Your 40-character RoutineHub API key.",
        "schema": {
          "type": "string"
        }
      },
      "ShortcutId": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "Numeric shortcut id (from the shortcut URL).",
        "schema": {
          "type": "integer"
        }
      },
      "Slug": {
        "name": "slug",
        "in": "path",
        "required": true,
        "description": "The item's permalink slug.",
        "schema": {
          "type": "string"
        }
      }
    },
    "responses": {
      "Error": {
        "description": "Error envelope.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "ValidationError": {
        "description": "Invalid metadata; `errors` holds per-field messages.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ValidationErrorEnvelope"
            }
          }
        }
      },
      "RateLimited": {
        "description": "Daily publishing cap reached.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      }
    },
    "schemas": {
      "ErrorEnvelope": {
        "type": "object",
        "properties": {
          "result": {
            "type": "string",
            "example": "error"
          },
          "message": {
            "type": "string"
          }
        },
        "required": [
          "result",
          "message"
        ]
      },
      "ValidationErrorEnvelope": {
        "type": "object",
        "properties": {
          "result": {
            "type": "string",
            "example": "error"
          },
          "message": {
            "type": "string"
          },
          "errors": {
            "type": "object",
            "additionalProperties": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          }
        }
      },
      "MessageEnvelope": {
        "type": "object",
        "properties": {
          "result": {
            "type": "string",
            "enum": [
              "success",
              "error"
            ]
          },
          "message": {
            "type": "string"
          }
        }
      },
      "Deleted": {
        "type": "object",
        "properties": {
          "result": {
            "type": "string",
            "example": "success"
          },
          "deleted": {
            "type": "string",
            "description": "The deleted slug."
          }
        }
      },
      "VersionList": {
        "type": "object",
        "properties": {
          "result": {
            "type": "string",
            "example": "success"
          },
          "versions": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "version": {
                  "type": "string"
                },
                "changes": {
                  "type": "string"
                },
                "date": {
                  "type": "string",
                  "format": "date-time"
                }
              }
            }
          }
        }
      },
      "VersionCreated": {
        "type": "object",
        "properties": {
          "result": {
            "type": "string",
            "example": "success"
          },
          "slug": {
            "type": "string"
          },
          "version": {
            "type": "string"
          }
        }
      },
      "Skill": {
        "type": "object",
        "properties": {
          "slug": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "brief": {
            "type": "string"
          },
          "published": {
            "type": "boolean"
          },
          "url": {
            "type": "string"
          },
          "latest_version": {
            "type": [
              "string",
              "null"
            ]
          },
          "price_usd": {
            "type": [
              "string",
              "null"
            ],
            "description": "Current price as a decimal string, e.g. \"1.99\". Null if free."
          },
          "payout": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "stripe",
              "crypto_usdc_base",
              null
            ],
            "description": "Active payout rail. Null if free."
          }
        }
      },
      "Prompt": {
        "type": "object",
        "properties": {
          "slug": {
            "type": "string"
          },
          "command": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "brief": {
            "type": "string"
          },
          "kind": {
            "type": "string"
          },
          "published": {
            "type": "boolean"
          },
          "url": {
            "type": "string"
          },
          "latest_version": {
            "type": [
              "string",
              "null"
            ]
          },
          "price_usd": {
            "type": [
              "string",
              "null"
            ],
            "description": "Current price as a decimal string. Null if free."
          },
          "payout": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "stripe",
              "crypto_usdc_base",
              null
            ],
            "description": "Active payout rail. Null if free."
          }
        }
      },
      "Plugin": {
        "type": "object",
        "properties": {
          "slug": {
            "type": "string"
          },
          "package_name": {
            "type": "string"
          },
          "namespace": {
            "type": "string"
          },
          "role": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "brief": {
            "type": "string"
          },
          "published": {
            "type": "boolean"
          },
          "url": {
            "type": "string"
          },
          "version": {
            "type": [
              "string",
              "null"
            ]
          },
          "skills": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "prompts": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "price_usd": {
            "type": [
              "string",
              "null"
            ],
            "description": "Current price as a decimal string. Null if free."
          },
          "payout": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "stripe",
              "crypto_usdc_base",
              null
            ],
            "description": "Active payout rail. Null if free."
          }
        }
      }
    }
  }
}
