HTTP API Reference for ClawHub: Public and CLI Endpoints with Authentication
This page details the HTTP API for ClawHub, including public read endpoints, CLI endpoints, and authentication. It covers rate limiting, base URLs, and best practices for third-party integrations.
Read this when
- Adding/changing endpoints
- Debugging CLI ↔ registry requests
HTTP API
Default base URL: https://clawhub.ai.
All version 1 paths live under /api/v1/....
For backward compatibility, the legacy /api/... and /api/cli/... endpoints remain available (details in DEPRECATIONS.md).
OpenAPI specification: /api/v1/openapi.json.
Public catalog reuse
Public read endpoints allow third party directories to list or search ClawHub skills. Cache results, respect 429 and Retry-After, direct users to the official ClawHub listing (https://clawhub.ai/<owner>/skills/<slug>), and never suggest ClawHub endorses your site. Do not mirror any content that is hidden, private, or blocked by moderation outside the public API.
Web slug shortcuts work across registry families, but API clients should prefer the canonical URLs returned by read endpoints rather than reconstructing route precedence.
Rate limits
How enforcement works:
-
Anonymous requests: rate limited per IP address.
-
Authenticated requests (valid Bearer token): rate limited per user bucket.
-
When the token is missing or invalid, the system falls back to IP based enforcement.
-
Authenticated write endpoints should not respond with a bare
Unauthorizedwhen the server knows the cause. Missing tokens, invalid or revoked tokens, and deleted, banned, or disabled accounts should each produce descriptive text so CLI clients can inform users about the block. -
Read: 3000 per minute per IP, 12000 per minute per key
-
Write: 300 per minute per IP, 3000 per minute per key
-
Download: 1200 per minute per IP, 6000 per minute per key (download endpoints)
Headers:
- Legacy compatibility:
X-RateLimit-Limit,X-RateLimit-Reset - Standardized:
RateLimit-Limit,RateLimit-Reset - On
429:X-RateLimit-Remaining: 0andRateLimit-Remaining: 0 - On
429:Retry-After
What the headers mean:
X-RateLimit-Reset: absolute Unix epoch secondsRateLimit-Reset: seconds remaining until reset (delay)X-RateLimit-Remaining/RateLimit-Remaining: exact remaining budget when present. Sharded successful requests omit this header instead of providing an approximate global value.Retry-After: seconds to wait before retry (delay) on429
Example 429 response:
HTTP/2 429
content-type: text/plain; charset=utf-8
x-ratelimit-limit: 20
x-ratelimit-remaining: 0
x-ratelimit-reset: 1771404540
ratelimit-limit: 20
ratelimit-remaining: 0
ratelimit-reset: 34
retry-after: 34
Rate limit exceeded
Client guidance:
- If
Retry-Afteris present, pause for that many seconds before retrying. - Use jittered backoff to prevent synchronized retries.
- When
Retry-Afteris absent, fall back toRateLimit-Reset(or derive it fromX-RateLimit-Reset).
IP source:
- Trusted client IP headers, such as
cf-connecting-ip, are used only when the deployment explicitly enables trusted forwarded headers. - ClawHub uses trusted forwarding headers at the edge to identify client IPs.
- Without a trusted client IP, anonymous requests fall back to buckets scoped solely by rate limit kind. These fallback buckets do not include caller supplied paths, slugs, package names, versions, query strings, or other artifact parameters.
Error responses
Public v1 error responses are plain text with content-type: text/plain; charset=utf-8.
This covers validation failures (400), missing public resources (404), authentication and permission failures (401/403), rate limits (429), and blocked downloads. Clients should treat the response body as a human readable string. Unknown query parameters are silently ignored for compatibility, but recognized query parameters with invalid values return 400.
Public endpoints (no auth)
GET /api/v1/search
Query parameters:
q(required): query stringlimit(optional): integerhighlightedOnly(optional):trueto restrict results to highlighted skillsnonSuspiciousOnly(optional):trueto exclude suspicious (flagged.suspicious) skillsnonSuspicious(optional): legacy alias fornonSuspiciousOnly
Response:
{
"results": [
{
"score": 0.123,
"slug": "gifgrep",
"displayName": "GifGrep",
"summary": "…",
"version": "1.2.3",
"updatedAt": 1730000000000,
"ownerHandle": "openclaw",
"owner": {
"handle": "openclaw",
"displayName": "OpenClaw",
"image": "https://example.com/avatar.png"
}
}
]
}
Notes:
- Results are ordered by relevance, which combines embedding similarity with exact boosts for slug and name token matches, plus a small popularity prior.
- Relevance takes precedence over popularity. A direct match on a slug or display-name token can beat a less precise match even if the latter has significantly higher engagement.
- ASCII text is split into tokens at word and punctuation boundaries. For instance,
personal-mapincludes the tokenmapby itself, whileamap-jsapi-skillcontainsamap,jsapi, andskill; a search formapgivespersonal-mapa stronger lexical match thanamap-jsapi-skill. - Popularity uses a log scale with a cap. Skills with high engagement can still rank lower if the query text does not match as closely.
- Skills in a suspicious or hidden moderation state may be excluded from public search results, depending on the caller's filters and the current moderation status.
Guidance for publisher discoverability:
- Place the exact terms users will search for in the display name, summary, and tags. Use a standalone slug token only when it also serves as a stable identifier you intend to keep.
- Do not rename a slug just to match a single query unless the new slug is a better long-term canonical name. Old slugs become redirect aliases, but the canonical URL, displayed slug, and future search digests all use the new slug.
- Rename aliases keep old URLs and installs that resolve through the registry working, but search ranking is based on the canonical skill metadata after the rename is indexed. Existing statistics remain with the skill.
- If a skill is unexpectedly missing from results, first check its moderation state using
clawhub inspect @owner/slugwhile logged in, before changing any ranking-related metadata.
GET /api/v1/skills
Query parameters:
limit(optional): integer (1, 200)cursor(optional): pagination cursor for any sort other thantrendingsort(optional):updated(default),recommended(alias:default),createdAt(alias:newest),downloads,stars(alias:rating), legacy install aliasesinstallsCurrent/installs/installsAllTimemap todownloads,trendingnonSuspiciousOnly(optional):trueto hide suspicious (flagged.suspicious) skillsnonSuspicious(optional): legacy alias fornonSuspiciousOnly
Invalid sort values return 400.
Notes:
recommendeduses engagement and recency signals.trendingranks by installs from the last 7 days (based on telemetry).createdAtis stable for crawling new skills;updatedchanges when existing skills are republished.- When
nonSuspiciousOnly=true, cursor-based sorts may return fewer thanlimititems per page because suspicious skills are filtered after the page is retrieved. - Use
nextCursorto continue pagination when it is present. A short page does not necessarily indicate the end of results.
Response:
{
"items": [
{
"slug": "gifgrep",
"displayName": "GifGrep",
"summary": "…",
"topics": ["Productivity"],
"tags": { "latest": "1.2.3" },
"stats": {},
"createdAt": 0,
"updatedAt": 0,
"latestVersion": { "version": "1.2.3", "createdAt": 0, "changelog": "…" },
"metadata": { "os": ["macos"], "systems": ["aarch64-darwin"] }
}
],
"nextCursor": null
}
GET /api/v1/skills/{slug}
Response:
{
"skill": {
"slug": "gifgrep",
"displayName": "GifGrep",
"summary": "…",
"topics": ["Productivity"],
"tags": { "latest": "1.2.3" },
"stats": {},
"createdAt": 0,
"updatedAt": 0
},
"latestVersion": { "version": "1.2.3", "createdAt": 0, "changelog": "…" },
"metadata": { "os": ["macos"], "systems": ["aarch64-darwin"] },
"owner": { "handle": "steipete", "displayName": "Peter", "image": null },
"moderation": {
"isSuspicious": false,
"isMalwareBlocked": false,
"verdict": "clean",
"reasonCodes": [],
"summary": null,
"engineVersion": "v2.0.0",
"updatedAt": 0
}
}
Notes:
- Old slugs created through owner rename or merge flows resolve to the canonical skill.
metadata.os: OS restrictions declared in the skill frontmatter (e.g.["macos"],["linux"]).nullif not declared.metadata.systems: Nix system targets (e.g.["aarch64-darwin", "x86_64-linux"]).nullif not declared.metadataisnullif the skill has no platform metadata.moderationis included only when the skill is flagged or the owner is viewing it.
GET /api/v1/skills/{slug}/moderation
Returns structured moderation state.
Response:
{
"moderation": {
"isSuspicious": true,
"isMalwareBlocked": false,
"verdict": "suspicious",
"reasonCodes": ["suspicious.dynamic_code_execution"],
"summary": "Detected: suspicious.dynamic_code_execution",
"engineVersion": "v2.0.0",
"updatedAt": 0,
"legacyReason": null,
"evidence": [
{
"code": "suspicious.dynamic_code_execution",
"severity": "critical",
"file": "index.ts",
"line": 3,
"message": "Dynamic code execution detected.",
"evidence": ""
}
]
}
}
Notes:
- Owners and moderators can access moderation details for hidden skills.
- Public callers only receive
200for already flagged visible skills. - Evidence is redacted for public callers and includes only raw snippets for owners and moderators.
POST /api/v1/skills/{slug}/report
Submit a skill for moderator review. Reports target a specific skill, can optionally reference a version, and feed into the skill report queue.
Authentication:
- An API token is required.
Request:
{ "reason": "Suspicious install step", "version": "1.2.3" }
Response:
{
"ok": true,
"reported": true,
"alreadyReported": false,
"reportId": "skillReports:...",
"skillId": "skills:...",
"reportCount": 1
}
GET /api/v1/skills/-/reports
Endpoint for moderators and administrators to retrieve skill reports.
Query parameters:
status(optional): acceptsopen(default),confirmed,dismissed, oralllimit(optional): an integer between 1 and 200cursor(optional): cursor for pagination
Response:
{
"items": [
{
"reportId": "skillReports:...",
"skillId": "skills:...",
"skillVersionId": "skillVersions:...",
"slug": "gifgrep",
"displayName": "GifGrep",
"version": "1.2.3",
"reason": "Suspicious install step",
"status": "open",
"createdAt": 1730000000000,
"reporter": {
"userId": "users:...",
"handle": "reporter",
"displayName": "Reporter"
},
"triagedAt": null,
"triagedBy": null,
"triageNote": null
}
],
"nextCursor": null,
"done": true
}
POST /api/v1/skills/-/reports/{reportId}/triage
Endpoint for moderators and administrators to resolve or reopen skill reports.
Request body:
{ "status": "confirmed", "note": "Reviewed and hid affected version.", "finalAction": "hide" }
When using confirmed or dismissed, note is mandatory. It can be left out when
returning status to open. To hide the skill through the same auditable workflow, pass finalAction: "hide" along with a triaged report.
GET /api/v1/skills/{slug}/versions
Query parameters:
limit(optional): integercursor(optional): pagination cursor
GET /api/v1/skills/{slug}/versions/{version}
Provides version metadata along with a file listing.
- When available,
version.securitycontains normalized scan verification status and scanner details (VirusTotal and LLM).
GET /api/v1/skills/{slug}/scan
Provides security scan verification details for a specific skill version.
Query parameters:
version(optional): a specific version string.tag(optional): resolves a tagged version, for instancelatest.
Notes:
- If neither
versionnortagis supplied, the latest version is used. - Includes normalized verification status and scanner-specific details.
security.hasScanResultis set totrueonly when a scanner produced a conclusive verdict (clean,suspicious, ormalicious).moderationrepresents a current moderation snapshot at the skill level, derived from the latest version.- When querying a historical version, review
moderation.matchesRequestedVersionandmoderation.sourceVersionbefore treatingmoderationandsecurityas belonging to the same version context.
POST /api/v1/skills/-/scan
Authenticated submission endpoint for creating new ClawScan jobs.
Local upload scans are no longer supported. Requests containing
multipart/form-data or { "source": { "kind": "upload" } } result in 410.
Published scans are submitted as JSON:
{
"source": { "kind": "published", "slug": "gifgrep", "version": "1.2.3" },
"update": false
}
Notes:
- Scan request payloads and downloadable reports are removed from the scan-request store once the retention period expires.
- Published scans require owner or publisher management access, or platform moderator/admin privileges.
- Published scans write back only when
update: trueis present and the scan finishes successfully. - The response is
202with{ "ok": true, "scanId": "...", "jobId": "...", "status": "queued", "sourceKind": "published", "update": false, "queue": { "queuedAhead": 0, "queuedAheadIsEstimate": false, "position": 1, "running": 0, "runningIsEstimate": false, "note": "Scans are asynchronous and may take time to complete." } }. - Scan jobs run asynchronously. Manual scan requests receive priority over normal publish or backfill work, but completion still depends on worker availability.
GET /api/v1/skills/-/scan/{scanId}
Authenticated polling endpoint for a submitted scan.
- Returns queued, running, succeeded, or failed status.
- While queued, returns
queue.queuedAheadandqueue.positionso clients can see how many prioritized manual scans are ahead of the request. Very large queues are capped and reported usingqueuedAheadIsEstimate: true. - When available,
reportincludesclawscan,skillspector,staticAnalysis, andvirustotalsections. - Failed scan jobs return
status: "failed"withlastError.
GET /api/v1/skills/-/scan/{scanId}/download
Authenticated endpoint for archiving reports.
- A scan must have succeeded; scans that are not terminal return
409. - The response is a ZIP containing
manifest.json,clawscan.json,skillspector.json,static-analysis.json,virustotal.json, andREADME.md.
GET /api/v1/skills/-/scan/download/{name}?version=<version>&kind=skill|plugin
Authenticated endpoint for downloading stored report archives of submitted versions.
- Requires owner or publisher management access to the skill or plugin, or platform moderator or admin privileges.
- Returns stored scan results for the exact version that was submitted, including versions that are blocked or hidden.
kindhas a default ofskill; for plugin or package scans, usekind=plugin.- The ZIP structure matches what scan-request downloads produce.
POST /api/v1/skills/-/scan/batch
Admin-only route for canonical batch rescans. It uses the same payload structure as the legacy POST /api/v1/skills/-/rescan-batch.
POST /api/v1/skills/-/scan/batch/status
Admin-only route for canonical batch status. It accepts { "jobIds": ["..."] } and provides the same aggregate counters as the legacy POST /api/v1/skills/-/rescan-batch/status.
GET /api/v1/skills/{slug}/verify
Returns the Skill Card verification envelope that clawhub skill verify uses.
Query parameters:
version(optional): a specific version string.tag(optional): resolves a tagged version, for examplelatest.
Notes:
okistrueonly when the chosen version has a generated Skill Card, is not blocked by moderation for malware, and ClawScan verification passes cleanly.- Skill identity, publisher identity, and metadata for the selected version are top-level envelope fields (
slug,displayName,publisherHandle,version,resolvedFrom,tag,createdAt), so shell automation can read them without unpacking nested wrappers. securityis the top-level ClawScan security verdict. Automation should rely onok,decision,reasons, andsecurity.status.security.signalscontains supporting scanner evidence likestaticScan,virusTotal, andskillSpector.security.signals.dependencyRegistryis kept for compatibility with v1 responses, but the dependency registry existence scanner has been retired and this key is alwaysnull.provenanceisserver-resolved-github-importonly when ClawHub resolved and stored a GitHub repo, ref, commit, or path during publish or import; otherwise it isunavailable.
POST /api/v1/skills/-/security-verdicts
Returns current compact security verdicts for exact skill versions. This collection endpoint is meant for clients that already know which installed ClawHub skill versions they need to show, for example the OpenClaw Control UI.
Request:
{
"items": [{ "slug": "gifgrep", "version": "1.2.3" }]
}
Notes:
itemsmust contain 1 to 100 unique{ slug, version }pairs.- Results are per item; a single missing skill or version does not cause the entire response to fail.
- The response covers security only. It does not include Skill Card data, generated card status, artifact file lists, or detailed scanner payloads.
security.signalscontains status-level supporting evidence only; use/scanor the ClawHub security audit page for full scanner details.security.signals.dependencyRegistryis kept for compatibility with v1 responses, but the dependency registry existence scanner has been retired and this key is alwaysnull.- The absence of a Skill Card does not affect this endpoint's
ok,decision, orreasons; clients should read installedskill-card.mdlocally when they need card content. - Use
/verifywhen you need the single-skill Skill Card verification envelope,/cardwhen you need generated card markdown, and/scanwhen you need detailed scanner data.
Response:
{
"schema": "clawhub.skill.security-verdicts.v1",
"items": [
{
"ok": true,
"decision": "pass",
"reasons": [],
"requestedSlug": "gifgrep",
"slug": "gifgrep",
"displayName": "GifGrep",
"publisherHandle": "steipete",
"publisherDisplayName": "Peter",
"requestedVersion": "1.2.3",
"version": "1.2.3",
"createdAt": 0,
"checkedAt": 0,
"skillUrl": "https://clawhub.ai/steipete/skills/gifgrep",
"securityAuditUrl": "https://clawhub.ai/steipete/skills/gifgrep/security-audit?version=1.2.3",
"security": {
"status": "clean",
"passed": true,
"signals": {
"staticScan": { "status": "clean", "reasonCodes": [] },
"virusTotal": null,
"skillSpector": null,
"dependencyRegistry": null
}
}
},
{
"ok": false,
"decision": "fail",
"reasons": ["version.not_found"],
"requestedSlug": "missing-version",
"requestedVersion": "1.0.0",
"error": { "code": "version_not_found", "message": "Version not found" },
"security": null
}
]
}
GET /api/v1/skills/{slug}/file
Delivers the exact bytes of a stored file as a downloadable response. To request a bounded escaped-text preview, append preview=1; any file whose bytes form valid UTF-8 can be previewed, irrespective of its file extension or MIME metadata.
Query parameters:
path(mandatory)version(optional)tag(optional)preview=1(optional; when the bytes are not valid UTF-8, returnstext/plainor415)
Remarks:
- The latest version is served by default.
- Raw downloads are capped at 10MB.
- Text previews are capped at 200KB.
GET /api/v1/packages
Single catalog endpoint covering:
- skills
- code plugins
- bundle plugins
Query parameters:
limit(optional): an integer between 1 and 100cursor(optional): cursor for paginationfamily(optional): one ofskill,code-plugin, orbundle-pluginchannel(optional): one ofofficial,community, orprivateisOfficial(optional): eithertrueorfalsesort(optional):updated(the default),recommended,trending,downloads, or the legacy aliasinstallscategory(optional): a plugin category filter. This filter is supported only when the request is limited to plugin packages (/api/v1/plugins,/api/v1/code-plugins,/api/v1/bundle-plugins, or package endpoints usingfamily=code-plugin/family=bundle-plugin). Controlled categories and legacy v1 filter aliases are listed underGET /api/v1/plugins.
Remarks:
- Providing invalid values for
family,channel,isOfficial,featured,highlightedOnly, orsortresults in400. Unrecognised query parameters are silently ignored. GET /api/v1/code-pluginsandGET /api/v1/bundle-pluginsremain fixed-family aliases.- Skill entries continue to be backed by the skill registry and can still be published exclusively through
POST /api/v1/skills. POST /api/v1/packagesremains restricted to code-plugin and bundle-plugin releases.- Unauthenticated callers see only public package channels.
- Authenticated callers see private packages for publishers they belong to in list and search results.
channel=privatereturns only those packages the authenticated caller is permitted to read.
GET /api/v1/packages/search
Unified catalog search across skills and plugin packages.
Query parameters:
q(required): query stringlimit(optional): integer (1, 100)family(optional):skill,code-plugin, orbundle-pluginchannel(optional):official,community, orprivateisOfficial(optional):trueorfalsecategory(optional): plugin category filter. This only applies when the request targets plugin packages. SeeGET /api/v1/pluginsfor controlled categories and legacy v1 filter aliases.
Notes:
- Supplying an invalid value for
family,channel,isOfficial,featured, orhighlightedOnlyresults in400. Unrecognized query parameters are silently dropped. - Unauthenticated users can only view public package channels.
- Authenticated users can search private packages for publishers they are a member of.
channel=privaterestricts results to packages the authenticated caller has read access to.
GET /api/v1/plugins
Browse the plugin catalog, which covers code-plugin and bundle-plugin packages.
Query params:
limit(optional): integer (1-100)cursor(optional): pagination cursorisOfficial(optional):trueorfalsesort(optional):recommended(default),trending,downloads,updated, legacy aliasinstallscategory(optional): plugin category filter. Current options:channels,models,memory,context,voice,media,web,tools,runtime,gateway,security,other.
Read endpoints still accept legacy v1 filter aliases:
mcp-tooling,data, andautomationmap totools.observabilityanddeploymentmap togateway.dev-toolsmaps toruntime.
trending shows a seven-day install and download leaderboard, not lifetime totals.
On the combined /api/v1/packages endpoint it applies only to plugins; use
/api/v1/skills?sort=trending for the skill catalog.
Legacy aliases are not accepted as stored or author-declared category values.
GET /api/v1/skills/export
Bulk export of the most recent public skills for offline analysis.
Authentication:
- An API token is mandatory.
Query parameters:
startDate(required): the lower bound for skillupdatedAt, expressed in Unix milliseconds.endDate(required): the upper bound for skillupdatedAt, expressed in Unix milliseconds.limit(optional): an integer between 1 and 250, with a default of250.cursor(optional): a pagination cursor taken from the prior response.
Response:
- The response body is a ZIP archive.
- Each exported skill is placed under the directory
{publisher}/{slug}/. - Skills hosted on the platform include the most recent stored version files and are recorded in
_manifest.jsonalongsidesourceRef: "public-clawhub". - Skills currently backed by GitHub that have undergone a
cleanorsuspiciousscan include_source_handoff.jsontogether withsourceRef: "public-github", the repository, commit, path, content hash, and archive URL. These do not contain source files hosted by ClawHub. - Every skill includes
_export_skill_meta.json. _manifest.jsonis always present at the root of the ZIP archive._errors.jsonis added when certain skills or files could not be exported.
Headers:
X-Next-CursorX-Has-MoreX-Total-ReturnedX-Date-RangeX-Export-Errors
GET /api/v1/plugins/export
Bulk export of the most recent public plugin releases for offline analysis.
Authentication:
- An API token is mandatory.
Query parameters:
startDate(required): the lower bound for pluginupdatedAt, expressed in Unix milliseconds.endDate(required): the upper bound for pluginupdatedAt, expressed in Unix milliseconds.limit(optional): an integer between 1 and 250, with a default of250.cursor(optional): a pagination cursor taken from the prior response.family(optional): eithercode-pluginorbundle-plugin. When omitted, both plugin families are included.
Response:
- The response body is a ZIP archive.
- Each exported plugin is placed under the directory
{family}/{packageName}/. - For every exported plugin, the stored files from the latest release are included.
- Per-plugin export metadata is stored at
__clawhub_export/{family}/{packageName}/plugin_meta.json. _manifest.jsonis always present at the root of the ZIP archive._errors.jsonis added when certain plugins or files could not be exported.
Headers:
X-Next-CursorX-Has-MoreX-Total-ReturnedX-Date-RangeX-Export-Errors
GET /api/v1/plugins/search
Search limited to plugins, covering both code-plugin and bundle-plugin packages.
Query parameters:
q(required): query stringlimit(optional): integer (1-100)isOfficial(optional):trueorfalsecategory(optional): plugin category filter. Current values:channels,models,memory,context,voice,media,web,tools,runtime,gateway,security,other.
Notes:
- The legacy v1 filter aliases described under
GET /api/v1/pluginsare also allowed. - Category filtering operates as a genuine API filter backed by plugin category digest rows, not a search-query transformation.
- Results appear in relevance order and currently lack pagination.
- Browser UI sort controls for plugin search reorder the loaded relevance results,
matching the existing
/skillsbrowse behavior.
GET /api/v1/packages/{name}
Provides package detail metadata.
Notes:
- Skills can also be resolved through this route in the unified catalog.
- Private packages return
404unless the caller can read the owning publisher.
DELETE /api/v1/packages/{name}
Performs a soft-delete on a package and all its releases.
Notes:
- Requires an API token for the package owner, an org publisher owner/admin, platform moderator, or platform admin.
GET /api/v1/packages/{name}/versions
Returns version history.
Query params:
limit(optional): integer (1, 100)cursor(optional): pagination cursor
Notes:
- Private packages return
404unless the caller can read the owning publisher.
GET /api/v1/packages/{name}/versions/{version}
Returns a single package version, including file metadata, compatibility, verification, artifact metadata, and scan data.
Notes:
version.artifact.kindislegacy-zipfor old-world package archives ornpm-packfor ClawPack-backed releases.- ClawPack releases include npm-compatible
npmIntegrity,npmShasum, andnpmTarballNamefields. version.sha256hashholds deprecated compatibility metadata intended for older clients. It hashes the exact ZIP bytes returned by/api/v1/packages/{name}/download. Modern clients should useversion.artifact.sha256, which identifies the canonical release artifact.version.vtAnalysis,version.llmAnalysis, andversion.staticScanare included when scan data exists.- Private packages return
404unless the caller can read the owning publisher.
GET /api/v1/packages/{name}/versions/{version}/security
Returns the exact package release security and trust summary for install clients. This is the public OpenClaw consumption surface for deciding whether a resolved release can be installed.
Auth:
- Public read endpoint. No owner, publisher, moderator, or admin token is required.
Response:
{
"package": {
"name": "@openclaw/example-plugin",
"displayName": "Example Plugin",
"family": "code-plugin"
},
"release": {
"releaseId": "packageReleases:...",
"version": "1.2.3",
"artifactKind": "npm-pack",
"artifactSha256": "0123456789abcdef...",
"npmIntegrity": "sha512-...",
"npmShasum": "0123456789abcdef0123456789abcdef01234567",
"npmTarballName": "example-plugin-1.2.3.tgz",
"createdAt": 1730000000000
},
"trust": {
"scanStatus": "malicious",
"moderationState": "quarantined",
"blockedFromDownload": true,
"reasons": ["manual:quarantined", "scan:malicious"],
"pending": false,
"stale": false
}
}
Response fields:
package.name,package.displayName, andpackage.familypoint to the resolved registry package.- The exact release that underwent evaluation is indicated by
release.releaseId,release.version, andrelease.createdAt. - When available for the release artifact,
release.artifactKind,release.artifactSha256,release.npmIntegrity,release.npmShasum, andrelease.npmTarballNameare included. trust.scanStatusrepresents the final trust status, derived from scanner data and any manual release moderation applied.trust.moderationStatecan be null. It takes the valuenullwhen no manual moderation has been performed on the release.trust.blockedFromDownloadsignals whether installation should be blocked. Install clients such as OpenClaw ought to halt installation when this field equalstrue, rather than recalculating block rules from scanner or moderation fields.trust.reasonsprovides a list of explanations intended for users and audit logs. Reason codes are fixed, concise strings likemanual:quarantined,scan:malicious, andpackage:malicious.trust.pendingindicates that at least one trust input has not yet finished processing.trust.stalesignals that the trust summary was generated from stale inputs and should be refreshed before a high-confidence allow decision is made.
Notes:
- This endpoint is tied to a specific version. Clients should invoke it after determining the exact package version they plan to install, not simply after fetching the most recent package metadata.
- For private packages,
404is returned unless the caller has access to the owning publisher. - This endpoint has a deliberately narrower scope compared to owner or moderator moderation endpoints. It provides the installation decision and a public explanation, but does not expose reporter identities, report content, private evidence, or internal review timelines.
GET /api/v1/packages/{name}/versions/{version}/artifact
Returns the metadata for a package version resolved through the explicit artifact resolver.
Notes:
- Older package versions produce a
legacy-zipartifact along with a legacy ZIPdownloadUrl. - ClawPack versions yield an
npm-packartifact, npm integrity fields, atarballUrl, and the compatibility URL for the legacy ZIP. - This is the OpenClaw resolver interface; it avoids guessing the archive format from a shared URL.
GET /api/v1/packages/{name}/versions/{version}/artifact/download
Downloads the version artifact via the explicit resolver path.
Notes:
- ClawPack versions stream the exact uploaded npm-pack
.tgzbytes. - Legacy ZIP versions redirect to
/api/v1/packages/{name}/download?version=. - The download rate bucket governs this operation.
GET /api/v1/packages/{name}/readiness
Returns a computed readiness assessment for future OpenClaw consumption.
Readiness checks include:
- status of the official channel
- whether the latest version is available
- availability of the ClawPack npm-pack artifact
- artifact digest
- source repository and commit provenance
- OpenClaw compatibility metadata
- host targets
- scan state
Response:
{
"package": {
"name": "@openclaw/example-plugin",
"displayName": "Example Plugin",
"family": "code-plugin",
"isOfficial": true,
"latestVersion": "1.2.3"
},
"ready": false,
"checks": [
{
"id": "clawpack",
"label": "ClawPack artifact",
"status": "fail",
"message": "Latest version is legacy ZIP-only."
}
],
"blockers": ["clawpack"]
}
GET /api/v1/packages/migrations
Moderator endpoint for listing official OpenClaw plugin migration entries.
Auth:
- Requires an API token belonging to a moderator or admin user.
Query params:
phase(optional):planned,published,clawpack-ready,legacy-zip-only,metadata-ready,blocked,ready-for-openclaw, orall(default).limit(optional): integer between 1 and 100cursor(optional): pagination cursor
Response:
{
"items": [
{
"migrationId": "officialPluginMigrations:...",
"bundledPluginId": "core.search",
"packageName": "@openclaw/search-plugin",
"packageId": "packages:...",
"owner": "platform",
"sourceRepo": "openclaw/openclaw",
"sourcePath": "plugins/search",
"sourceCommit": "abc123",
"phase": "blocked",
"blockers": ["missing ClawPack"],
"hostTargetsComplete": true,
"scanClean": false,
"moderationApproved": false,
"runtimeBundlesReady": false,
"notes": null,
"createdAt": 1760000000000,
"updatedAt": 1760000000000
}
],
"nextCursor": null,
"done": true
}
POST /api/v1/packages/migrations
Admin endpoint for creating or updating an official plugin migration entry.
Auth:
- Requires an API token belonging to an admin user.
Request body:
{
"bundledPluginId": "core.search",
"packageName": "@openclaw/search-plugin",
"owner": "platform",
"sourceRepo": "openclaw/openclaw",
"sourcePath": "plugins/search",
"sourceCommit": "abc123",
"phase": "blocked",
"blockers": ["missing ClawPack"],
"hostTargetsComplete": true,
"scanClean": false,
"moderationApproved": false,
"runtimeBundlesReady": false,
"notes": "waiting on publisher upload"
}
Notes:
bundledPluginIdis normalized to lowercase and serves as the stable upsert key.packageNameis normalized according to npm naming conventions; the package may be absent for planned migrations.- This endpoint only tracks migration readiness. It does not modify OpenClaw or generate ClawPacks.
GET /api/v1/packages/moderation/queue
Moderator or admin endpoint for package release review queues.
Auth:
- Requires an API token belonging to a moderator or admin user.
Query params:
status(optional):open(default),blocked,manual, oralllimit(optional): integer between 1 and 100cursor(optional): pagination cursor
Status meanings:
open: releases marked as suspicious, malicious, pending, quarantined, revoked, or reported.blocked: releases that are quarantined, revoked, or malicious.manual: any release where a manual moderation override has been applied.all: any release with a manual override, a non-clean scan state, or a package report.
{
"items": [
{
"packageId": "packages:...",
"releaseId": "packageReleases:...",
"name": "@openclaw/example-plugin",
"displayName": "Example Plugin",
"family": "code-plugin",
"channel": "community",
"isOfficial": false,
"version": "1.2.3",
"createdAt": 1730000000000,
"artifactKind": "npm-pack",
"scanStatus": "malicious",
"moderationState": "quarantined",
"moderationReason": "manual review",
"sourceRepo": "openclaw/example-plugin",
"sourceCommit": "abc123",
"reportCount": 2,
"lastReportedAt": 1730000001000,
"reasons": ["manual:quarantined", "scan:malicious", "reports:2"]
}
],
"nextCursor": null,
"done": true
}
POST /api/v1/packages/{name}/report
Submit a package for moderator review. These reports apply at the package level and may optionally reference a specific version. They enter the moderation queue but do not automatically hide or block downloads; moderators should use release moderation to approve, quarantine, or revoke artifacts.
Auth:
- An API token is required.
Request:
{ "reason": "Suspicious native binary", "version": "1.2.3" }
Response:
{
"ok": true,
"reported": true,
"alreadyReported": false,
"packageId": "packages:...",
"releaseId": "packageReleases:...",
"reportCount": 1
}
GET /api/v1/packages/reports
Moderator or admin endpoint for accepting package reports.
Auth:
- Requires an API token belonging to a moderator or admin user.
Query params:
status(optional):open(default),confirmed,dismissed, oralllimit(optional): integer from 1 to 100cursor(optional): cursor for pagination
Response:
{
"items": [
{
"reportId": "packageReports:...",
"packageId": "packages:...",
"releaseId": "packageReleases:...",
"name": "@openclaw/example-plugin",
"displayName": "Example Plugin",
"family": "code-plugin",
"version": "1.2.3",
"reason": "Suspicious native binary",
"status": "open",
"createdAt": 1730000000000,
"reporter": {
"userId": "users:...",
"handle": "reporter",
"displayName": "Reporter"
},
"triagedAt": null,
"triagedBy": null,
"triageNote": null
}
],
"nextCursor": null,
"done": true
}
GET /api/v1/packages/{name}/moderation
Endpoint for owners and moderators to view package moderation status.
Auth:
- Requires an API token for the package owner, a publisher member, a moderator, or an admin user.
Response:
{
"package": {
"packageId": "packages:...",
"name": "@openclaw/example-plugin",
"displayName": "Example Plugin",
"family": "code-plugin",
"channel": "community",
"isOfficial": false,
"reportCount": 2,
"lastReportedAt": 1730000001000,
"scanStatus": "malicious"
},
"latestRelease": {
"releaseId": "packageReleases:...",
"version": "1.2.3",
"artifactKind": "npm-pack",
"scanStatus": "malicious",
"moderationState": "quarantined",
"moderationReason": "manual review",
"blockedFromDownload": true,
"reasons": ["manual:quarantined", "scan:malicious", "reports:2"],
"createdAt": 1730000000000
}
}
POST /api/v1/packages/reports/{reportId}/triage
Moderator or admin endpoint to resolve or reopen package reports.
Request:
{
"status": "confirmed",
"note": "Reviewed and quarantined affected release.",
"finalAction": "quarantine"
}
note is mandatory when using confirmed or dismissed; it can be left out when reverting status to open. To apply release moderation in the same auditable workflow, pass finalAction: "quarantine" or finalAction: "revoke" with a confirmed report.
Response:
{
"ok": true,
"reportId": "packageReports:...",
"packageId": "packages:...",
"status": "confirmed",
"reportCount": 0
}
POST /api/v1/packages/{name}/versions/{version}/moderation
Moderator or admin endpoint for reviewing a package release.
Request:
{ "state": "quarantined", "reason": "Suspicious native payload." }
Supported states:
approved: manually reviewed and approved.quarantined: blocked while awaiting follow-up.revoked: blocked after a previously trusted release.
Quarantined and revoked releases return 403 from artifact download routes. Every change is recorded in the audit log.
GET /api/v1/packages/{name}/file
Returns the exact stored package file bytes as a download. Append preview=1 to request the same bounded UTF-8 text preview used for skill files.
Query params:
path(required)version(optional)tag(optional)preview=1(optional; returnstext/plainor415when the bytes are not valid UTF-8)
Notes:
- Defaults to the most recent release.
- Uses the read rate bucket, not the download bucket.
- Raw download limit: 10 MB.
- Text preview limit: 200 KB; opaque files return
415only for preview requests. - Pending VirusTotal scans do not block reads; malicious releases may still be withheld elsewhere.
- Private packages return
404unless the caller can read the owning publisher.
GET /api/v1/packages/{name}/download
Downloads the legacy deterministic ZIP archive for a package release.
Query params:
version(optional)tag(optional)
Notes:
- Defaults to the most recent release.
- Skills redirect to
GET /api/v1/download. - Plugin and package archives use a
package/root directory in the zip file so older OpenClaw clients continue to function. - This route serves only ZIP files. It does not stream ClawPack
.tgzarchives. - Responses include
ETag,Digest,X-ClawHub-Artifact-Type, andX-ClawHub-Artifact-Sha256headers for resolver integrity checks. - Registry-only metadata is not injected into the downloaded archive.
- Pending VirusTotal scans do not block downloads; malicious releases return
403. - Private packages return
404unless the caller is the owner.
GET /api/npm/{package}
Returns an npm-compatible packument for package versions backed by ClawPack.
Notes:
- Only ClawPack npm-pack tarballs that have been uploaded are shown in the version list.
- Versions that only exist as legacy ZIP files are deliberately excluded.
dist.tarball,dist.integrity, anddist.shasumuse fields compatible with npm, so users can configure npm to target the mirror if desired.- Scoped package packuments support both
/api/npm/@scope/nameand npm's encoded/api/npm/@scope%2Fnamerequest path.
GET /api/npm/{package}/-/{tarball}.tgz
Sends the exact uploaded ClawPack tarball byte stream for npm mirror clients.
Notes:
- The download rate bucket applies here.
- Response headers include ClawHub SHA-256 along with npm integrity and shasum metadata.
- Moderation and private package access checks remain enforced.
GET /api/v1/resolve
The CLI uses this endpoint to associate a local fingerprint with a known version.
Query parameters:
slug(required)hash(required): a 64-character hex sha256 of the bundle fingerprint
Response:
{ "slug": "gifgrep", "match": { "version": "1.2.2" }, "latestVersion": { "version": "1.2.3" } }
GET /api/v1/download
Retrieves a hosted skill version ZIP, or provides a GitHub source handoff for a current GitHub-backed skill that has a clean or suspicious scan and no hosted version.
Query parameters:
slug(required)version(optional): a semver stringtag(optional): a tag name, for examplelatest
Notes:
- When neither
versionnortagis supplied, the most recent version is selected. - Soft-deleted versions return
410. - Handoffs for GitHub-backed skills do not proxy or mirror bytes. The JSON response contains
sourceRef: "public-github",repo,commit,path,contentHash, andarchiveUrl; the scan or current state acts as a gate and is not included as success payload metadata. - Download statistics are counted as unique identities per UTC day (
userIdwhen an API token is valid, otherwise by IP).
Auth endpoints (Bearer token)
Every endpoint requires:
Authorization: Bearer clh_...
GET /api/v1/whoami
Validates the token and returns the user handle.
POST /api/v1/skills
Creates and publishes a new version.
- Recommended approach:
multipart/form-datawithpayloadJSON andfiles[]blobs. - A JSON body that includes
files(storageId-based) is also acceptable. - Optional payload field:
ownerHandle. If present, the API resolves that publisher server-side and the actor must have publisher access. - Optional payload field:
migrateOwner. WhentruewithownerHandle, an existing skill can be transferred to that owner provided the actor is an admin or owner on both the current and target publishers. Without this opt-in, owner changes are denied.
POST /api/v1/packages
Publishes a code-plugin or bundle-plugin release.
- Bearer token authentication is required.
multipart/form-datais mandatory.- Allowed form fields include
payload, repeatedfilesblobs, or a singleclawpacktarball reference.clawpackcan be a.tgzblob or a storage id obtained from the upload-url flow. Staged storage-id publishes must also include theclawpackUploadTicketthat came with that upload URL. - Use either
filesorclawpack, but never both in one request. - JSON bodies and caller-supplied
payload.filesorpayload.artifactmetadata are not accepted. - Direct multipart publishes are limited to 18MB. ClawPack tarballs can use the upload-url flow up to the 120MB tarball limit.
- Optional payload field:
ownerHandle. When present, only admins may publish on behalf of that owner.
Validation highlights:
- The value of
familymust be eithercode-pluginorbundle-plugin. - Plugin packages depend on
openclaw.plugin.json. When uploading a ClawPack.tgz, that file must be placed atpackage/openclaw.plugin.json. - Code plugins require
package.json, source repo metadata, source commit metadata, config schema metadata,openclaw.compat.pluginApi, andopenclaw.build.openclawVersion. openclaw.hostTargetsandopenclaw.environmentare not required metadata.- Only the
openclaworg publisher and personal publishers of currentopenclaworg members are allowed to publish to theofficialchannel. - Even when publishing on behalf of someone else, official-channel eligibility is still validated against the target owner's account.
DELETE /api/v1/skills/{slug} / POST /api/v1/skills/{slug}/undelete
Soft-delete or restore a skill (available to owners, moderators, or admins).
Optional JSON body:
{ "reason": "Held for moderation pending legal review." }
If provided, reason gets saved as the skill moderation note and is also recorded in the audit log.
When an owner performs a soft delete, the slug stays reserved for 30 days; after that period, another publisher can claim it. The delete response includes slugReservedUntil when this reservation applies.
Moderator or admin soft deletes, as well as security removals, do not have an expiration on the slug.
Delete response:
{ "ok": true, "slugReservedUntil": 1730000000000 }
Status codes:
200: ok401: unauthorized403: forbidden404: skill or user not found500: internal server error
POST /api/v1/users/publisher
Restricted to admins. This endpoint guarantees an org publisher exists for a given handle. If the handle still references a legacy shared user or personal publisher, it is first migrated into an org publisher. For a new org, supply memberHandle; the acting admin is not added as a member.
memberRole defaults to owner.
- Body:
{ "handle": "openclaw", "displayName": "OpenClaw", "memberHandle": "alice", "memberRole": "owner", "trusted": true } - Response:
{ "ok": true, "publisherId": "...", "handle": "openclaw", "created": true, "migrated": false, "trusted": true, "member": { "userId": "...", "handle": "alice", "role": "owner" } }
POST /api/v1/publishers
Authenticated self-service creation of an org publisher. This creates a new org publisher and makes the caller an owner. It does not migrate existing user or personal handles, nor does it mark the publisher as trusted or official.
- Body:
{ "handle": "opik", "displayName": "Opik" } - Response:
{ "ok": true, "publisherId": "...", "handle": "opik", "created": true, "trusted": false } - Returns
409when the handle is already taken by a publisher, user, or personal publisher.
POST /api/v1/users/reserve
Admin-only. Reserves root slugs and package names for the rightful owner without requiring a release to be published. Package names become private placeholder packages with no release rows, so the same owner can later publish the actual code-plugin or bundle-plugin release into that name.
- Body:
{ "handle": "openclaw", "slugs": ["diffs"], "packageNames": ["@openclaw/diffs"], "reason": "reserved for official OpenClaw plugin" } - Response:
{ "ok": true, "succeeded": 2, "failed": 0, "results": [{ "kind": "slug", "name": "diffs", "ok": true, "action": "reserved" }] }
POST /api/v1/users/publisher-recovery
Admin-only. Recovers a personal publisher for a verified replacement GitHub OAuth principal without modifying Convex Auth account rows. The request must specify both immutable GitHub provider account IDs; mutable handles are used only as an operator-facing safeguard.
The endpoint runs in dry-run mode by default. To apply the recovery, dryRun: false and confirmIdentityVerified: true are required after staff independently verify continuity between the two GitHub principals. Recovery fails closed if the destination user's current personal publisher has any skills, packages, or GitHub skill sources.
Recovery also migrates legacy ownerUserId fields for the recovered publisher's skills, skill slug aliases, packages, package inspector warnings, and derived search digest rows so that direct-owner paths align with the new publisher authority. An active protected-handle reservation for the recovered handle is reassigned to the replacement user, preventing later profile synchronization from restoring the former user's competing authority. Each primary table is limited to 100 rows per apply transaction; larger recoveries must first use a resumable owner migration.
GitHub skill sources are publisher-scoped and reported as checked rather than rewritten.
- Body:
{ "handle": "gingiris", "nextUserHandle": "gingiris-1031", "previousGitHubProviderAccountId": "123", "nextGitHubProviderAccountId": "456", "reason": "Verified account continuity for issue #2555", "confirmIdentityVerified": true, "dryRun": false } - Response:
{ "ok": true, "dryRun": false, "recovered": true, "publisherId": "...", "handle": "gingiris", "previousUser": { "userId": "...", "handle": "gingiris", "nextHandle": "gingiris-recovered", "githubProviderAccountId": "123", "authAccountCount": 1 }, "nextUser": { "userId": "...", "handle": "gingiris-1031", "nextHandle": "gingiris", "githubProviderAccountId": "456", "authAccountCount": 1 }, "retiredPersonalPublisher": null, "resourceOwnerMigration": { "limitPerTable": 100, "skills": 1, "skillSlugAliases": 1, "packages": 0, "packageInspectorWarnings": 0, "githubSourcesChecked": 1, "handleReservations": 1 }, "identityVerified": true, "reason": "Verified account continuity for issue #2555" }
Owner slug management endpoints
POST /api/v1/skills/{slug}/rename- Body:
{ "newSlug": "new-canonical-slug" } - Response:
{ "ok": true, "slug": "new-canonical-slug", "previousSlug": "old-slug" }
- Body:
POST /api/v1/skills/{slug}/merge- Body:
{ "targetSlug": "canonical-target-slug" } - Response:
{ "ok": true, "sourceSlug": "old-slug", "targetSlug": "canonical-target-slug" }
- Body:
Notes:
- Both endpoints require API token authentication and only work for the skill owner.
renamekeeps the previous slug as a redirect alias.mergeconceals the source listing and redirects the source slug to the target listing.
Transfer ownership endpoints
POST /api/v1/skills/{slug}/transfer- Body:
{ "toUserHandle": "target_handle", "message": "optional" } - Response:
{ "ok": true, "transferId": "skillOwnershipTransfers:...", "toUserHandle": "target_handle", "expiresAt": 1730000000000 }
- Body:
POST /api/v1/skills/{slug}/transfer/acceptPOST /api/v1/skills/{slug}/transfer/rejectPOST /api/v1/skills/{slug}/transfer/cancel- Response (accept/reject/cancel):
{ "ok": true, "skillSlug": "demo-skill?" }
- Response (accept/reject/cancel):
GET /api/v1/transfers/incomingGET /api/v1/transfers/outgoing- Response shape:
{ "transfers": [{ "_id": "...", "skill": { "slug": "demo", "displayName": "Demo" }, "fromUser"|"toUser": { "handle": "..." }, "message": "...", "requestedAt": 0, "expiresAt": 0 }] }
- Response shape:
POST /api/v1/users/ban
Remove a user and permanently delete their owned skills (requires moderator or admin privileges).
Body:
{ "handle": "user_handle", "reason": "optional ban reason" }
or
{ "userId": "users_...", "reason": "optional ban reason" }
Response:
{ "ok": true, "alreadyBanned": false, "deletedSkills": 3 }
POST /api/v1/users/unban
Reverse a ban and reinstate eligible skills (admin only).
Body:
{ "handle": "user_handle", "reason": "optional unban reason" }
or
{ "userId": "users_...", "reason": "optional unban reason" }
Response:
{ "ok": true, "alreadyUnbanned": false, "restoredSkills": 3 }
POST /api/v1/users/reclassify-ban
Update the reason associated with an active ban without lifting the ban or restoring content (admin only). By default this operates as a dry run unless dryRun is set to false.
Body:
{ "handle": "user_handle", "reason": "bulk publishing spam", "dryRun": true }
or
{ "userId": "users_...", "reason": "bulk publishing spam", "dryRun": false }
Response:
{
"ok": true,
"dryRun": false,
"userId": "users_...",
"handle": "user_handle",
"previousReason": "malware auto-ban",
"nextReason": "bulk publishing spam",
"changed": true
}
POST /api/v1/users/role
Modify a user's role (admin only).
Body:
{ "handle": "user_handle", "role": "moderator" }
or
{ "userId": "users_...", "role": "admin" }
Response:
{ "ok": true, "role": "moderator" }
GET /api/v1/users
Retrieve or search users (admin only).
Query parameters:
q(optional): search termquery(optional): alternative name forqlimit(optional): maximum number of results (default 20, cap 200)
Response:
{
"items": [
{
"userId": "users_...",
"handle": "user_handle",
"displayName": "User",
"name": "User",
"role": "moderator"
}
],
"total": 1
}
POST /api/v1/stars/{slug} / DELETE /api/v1/stars/{slug}
Create or delete a Bookmark. The older stars endpoint and its response field names are kept for backward compatibility. Both operations are idempotent.
Responses:
{ "ok": true, "starred": true, "alreadyStarred": false }
{ "ok": true, "unstarred": true, "alreadyUnstarred": false }
Legacy CLI endpoints (deprecated)
Kept for compatibility with older CLI versions:
GET /api/cli/whoamiPOST /api/cli/upload-urlPOST /api/cli/publishPOST /api/cli/telemetry/installPOST /api/cli/skill/deletePOST /api/cli/skill/undelete
Check DEPRECATIONS.md for when these will be removed.
POST /api/cli/upload-url produces uploadUrl and uploadTicket. When a package publish stages a ClawPack tarball, the resulting storage identifier must be passed as clawpack and the returned ticket as clawpackUploadTicket.
Registry discovery (/.well-known/clawhub.json)
The CLI can retrieve registry and authentication settings from the site:
/.well-known/clawhub.json(JSON, recommended)/.well-known/clawdhub.json(older format)
Schema:
{ "apiBase": "https://clawhub.ai", "authBase": "https://clawhub.ai", "minCliVersion": "0.0.5" }
If you run a self-hosted instance, serve this file (or set CLAWHUB_REGISTRY explicitly; legacy CLAWDHUB_REGISTRY).