Skip to content

Marketplace Interface

@jrtilak-recall/marketplace-interface provides version 0.1 marketplace contracts, Zod schemas, and a client for discovering marketplaces and reading plugin metadata.

Terminal window
bun add @jrtilak-recall/marketplace-interface

Import only the side of the contract your project needs:

import {
MarketplaceInfoSchema,
PluginListResponseSchema,
PluginResponseSchema,
PluginVersionResponseSchema,
type MarketplaceInfoInput,
} from "@jrtilak-recall/marketplace-interface/server";
import {
createMarketplaceClient,
type MarketplaceInfo,
} from "@jrtilak-recall/marketplace-interface/client";
Entry pointUse
/serverSchemas plus server input and validated output types. It does not include the HTTP client.
/clientThe marketplace client, client options, and validated response types.
Package rootBoth client and contract exports when bundle separation is unnecessary.

A marketplace begins with an HTTP or HTTPS discovery URL. Its response identifies the marketplace and provides route templates for later requests.

import {
MarketplaceInfoSchema,
type MarketplaceInfoInput,
} from "@jrtilak-recall/marketplace-interface/server";
const marketplace = {
name: "Example Marketplace",
description: "Plugins published by Example.",
namespace: "example",
baseUrl: "https://market.example/api/",
urls: {
listPlugins: "plugins?q=<query>",
getPluginByName: "plugins/<plugin-name>",
getPluginVersion: "plugins/<plugin-name>/<plugin-version>",
},
} satisfies MarketplaceInfoInput;
return Response.json(MarketplaceInfoSchema.parse(marketplace));

Templates support <query>, <plugin-name>, and <plugin-version>. They may be relative to baseUrl or use an absolute HTTP or HTTPS URL.

The client validates every response before returning it and encodes route placeholder values automatically.

import { createMarketplaceClient } from "@jrtilak-recall/marketplace-interface/client";
const client = createMarketplaceClient();
const marketplace = await client.getMarketplaceInfo(
"https://market.example/api/",
);
const plugins = await client.listPlugins(marketplace, { search: "theme" });
const plugin = await client.getPluginByName(
marketplace,
"@example/ocean-theme",
);
const version = await client.getPluginVersion(
marketplace,
plugin.name,
plugin.latestVersion,
);

Use createMarketplaceClient({ fetch }) to supply authentication, instrumentation, request policies, or a platform-specific Fetch implementation. Marketplace is the shared client that uses globalThis.fetch.

Version responses include the exact plugin manifest so clients can inspect the entry and permissions before downloading the archive:

{
"version": "1.0.0",
"size": 923,
"downloadUrl": "plugins/%40example%2Focean-theme/1.0.0/plugin.zip",
"manifest": {
"name": "@example/ocean-theme",
"displayName": "Ocean Theme",
"version": "1.0.0",
"author": "Example",
"recall": {
"manifestVersion": "0.1",
"permissions": ["registry.theme"],
"entry": {
"runtime": "theme",
"file": "theme.json"
}
}
},
"createdAt": "2026-07-12T00:00:00.000Z"
}

Manifest fields follow the Plugin Schema. The response version must match manifest.version.

  • Public URLs and requests use HTTP or HTTPS. Route and archive locations may also be relative to the marketplace base URL.
  • Marketplace namespaces and publisher usernames use bounded identifiers so generated plugin IDs remain unambiguous.
  • JSON responses are limited to 10 MiB and validated before client-side IDs are derived.
  • Detail and version responses must match the plugin and version requested by the client.

Absolute cross-origin HTTP routes remain supported for object storage and other marketplace infrastructure. Applications should trust or allowlist marketplaces, or inject a restricted Fetch implementation when network policy must be enforced.

See Build a Marketplace Server for the complete route examples and the Marketplace Interface changelog for release history.