Reference
HTTP API
HTTP API
Base URL: https://clawhub.ai (default).
All v1 paths are under /api/v1/....
Legacy /api/... and /api/cli/... remain for compatibility (see DEPRECATIONS.md).
OpenAPI: /api/v1/openapi.json.
Public catalog reuse
Third-party directories may use the public read endpoints to list or search ClawHub skills. Please cache results, honor 429/Retry-After, link users back to the canonical ClawHub listing (https://clawhub.ai/<owner>/<slug>), and avoid implying ClawHub endorsement of the third-party site. Do not attempt to mirror hidden, private, or moderation-blocked content outside the public API surface.
Web slug shortcuts resolve across registry families, but API clients should use the canonical URLs returned by read endpoints instead of reconstructing route precedence.
Rate limits
Enforcement model:
-
Anonymous requests: enforced per IP.
-
Authenticated requests (valid Bearer token): enforced per user bucket.
-
If token is missing/invalid, behavior falls back to IP enforcement.
-
Authenticated write endpoints should not return a bare
Unauthorizedwhen the server knows the reason. Missing tokens, invalid/revoked tokens, and deleted/banned/disabled accounts should each get actionable text so CLI clients can tell users what blocked them. -
Read: 3000/min per IP, 12000/min per key
-
Write: 300/min per IP, 3000/min per key
-
Download: 1200/min per IP, 6000/min per key (download endpoints)
Headers:
- Legacy compatibility:
X-RateLimit-Limit,X-RateLimit-Remaining,X-RateLimit-Reset - Standardized:
RateLimit-Limit,RateLimit-Remaining,RateLimit-Reset - On
429:Retry-After
Header semantics:
X-RateLimit-Reset: absolute Unix epoch secondsRateLimit-Reset: seconds until reset (delay)Retry-After: seconds to wait before retry (delay) on429
Example 429 response:
HTTP/2 429content-type: text/plain; charset=utf-8x-ratelimit-limit: 20x-ratelimit-remaining: 0x-ratelimit-reset: 1771404540ratelimit-limit: 20ratelimit-remaining: 0ratelimit-reset: 34retry-after: 34 Rate limit exceededClient guidance:
- If
Retry-Afterexists, wait that many seconds before retry. - Use jittered backoff to avoid synchronized retries.
- If
Retry-Afteris missing, fallback toRateLimit-Reset(or compute fromX-RateLimit-Reset).
IP source:
- Uses
cf-connecting-ip(Cloudflare) for client IP by default. - ClawHub uses trusted forwarding headers to identify client IPs at the edge.
- If no trusted client IP is available, anonymous download requests use an endpoint-scoped fallback bucket instead of one global
ip:unknownbucket. Anonymous read/write requests still use the shared unknown bucket so missing-IP routing remains visible and conservative.
Error responses
Public v1 error responses are plain text with content-type: text/plain; charset=utf-8.
This includes validation failures (400), missing public resources (404), auth and
permission failures (401/403), rate limits (429), and blocked downloads. Clients
should read the response body as a human-readable string. Unknown query parameters are
ignored for compatibility, but recognized query parameters with invalid values return
400.
Public endpoints (no auth)
GET /api/v1/search
Query params:
q(required): query stringlimit(optional): integerhighlightedOnly(optional):trueto filter to highlighted skillsnonSuspiciousOnly(optional):trueto hide 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 returned in relevance order (embedding similarity + exact slug/name token boosts + a small popularity prior from stars and downloads).
- Relevance is stronger than popularity. A precise slug or display-name token match can outrank a looser match with many more downloads.
- ASCII text is tokenized on word and punctuation boundaries. For example,
personal-mapcontains a standalonemaptoken, whileamap-jsapi-skillcontainsamap,jsapi, andskill; searching formaptherefore givespersonal-mapa stronger lexical match thanamap-jsapi-skill. - Popularity is log-scaled and capped. Stars carry the strongest weight, and downloads are the fallback popularity signal. High-download skills can rank lower when the query text is a weaker match.
- Suspicious or hidden moderation state can remove a skill from public search depending on caller filters and current moderation status.
Publisher discoverability guidance:
- Put the terms users will literally search for in the display name, summary, and tags. Use a standalone slug token only when it is also a stable identity you want to keep.
- Do not rename a slug just to chase one 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 use the new slug.
- Rename aliases preserve resolution for old URLs and installs that resolve through the registry, but search ranking is based on the canonical skill metadata after the rename has indexed. Existing stats stay with the skill.
- If a skill is unexpectedly invisible, check moderation state first with
clawhub inspect <slug>while logged in before changing ranking-related metadata.
GET /api/v1/skills
Query params:
limit(optional): integer (1β200)cursor(optional): pagination cursor for any non-trendingsortsort(optional):updated(default),recommended(alias:default),createdAt(alias:newest),downloads,stars(alias:rating),installsCurrent(alias:installs),installsAllTime,trendingnonSuspiciousOnly(optional):trueto hide suspicious (flagged.suspicious) skillsnonSuspicious(optional): legacy alias fornonSuspiciousOnly
Invalid sort values return 400.
Notes:
recommendedranks by stars, then downloads, thenupdatedAt.trendingranks by installs in the last 7 days (telemetry-based).createdAtis stable for new-skill crawls;updatedchanges when existing skills are republished.- When
nonSuspiciousOnly=true, cursor-based sorts may return fewer thanlimititems on a page because suspicious skills are filtered after page retrieval. - Use
nextCursorto continue pagination when present. A short page does not by itself mean end-of-results.
Response:
{ "items": [ { "slug": "gifgrep", "displayName": "GifGrep", "summary": "β¦", "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": "β¦", "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 by owner rename/merge flows resolve to the canonical skill.
metadata.os: OS restrictions declared in 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 get
200for already-flagged visible skills. - Evidence is redacted for public callers and only includes raw snippets for owners/moderators.
POST /api/v1/skills/{slug}/report
Report a skill for moderator review. Reports are skill-level, optionally linked to a version, and feed the skill report queue.
Auth:
- Requires an API token.
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
Moderator/admin endpoint for skill report intake.
Query params:
status(optional):open(default),confirmed,dismissed, oralllimit(optional): integer (1-200)cursor(optional): pagination cursor
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
Moderator/admin endpoint for resolving or reopening skill reports.
Request:
{ "status": "confirmed", "note": "Reviewed and hid affected version.", "finalAction": "hide" }note is required for confirmed and dismissed; it may be omitted when
setting status back to open. Pass finalAction: "hide" with a triaged
report to hide the skill in the same auditable workflow.
GET /api/v1/skills/{slug}/versions
Query params:
limit(optional): integercursor(optional): pagination cursor
GET /api/v1/skills/{slug}/versions/{version}
Returns version metadata + files list.
version.securityincludes normalized scan verification status and scanner details (VirusTotal + LLM), when available.
GET /api/v1/skills/{slug}/scan
Returns security scan verification details for a skill version.
Query params:
version(optional): specific version string.tag(optional): resolve a tagged version (for examplelatest).
Notes:
- If neither
versionnortagis provided, uses the latest version. - Includes normalized verification status plus scanner-specific details.
security.capabilityTagsincludes deterministic capability/risk labels such ascrypto,financial-authority,requires-wallet,can-make-purchases,can-sign-transactions,requires-paid-service,requires-oauth-token, andposts-externallywhen detected.security.hasScanResultistrueonly when a scanner produced a definitive verdict (clean,suspicious, ormalicious).moderationis a current skill-level moderation snapshot derived from the latest version.- When querying a historical version, check
moderation.matchesRequestedVersionandmoderation.sourceVersionbefore treatingmoderationandsecurityas the same version context.
POST /api/v1/skills/-/scan
Authenticated submit endpoint for new ClawScan jobs.
Local upload scans are no longer supported. Requests using
multipart/form-data or { "source": { "kind": "upload" } } return 410.
Published scans use JSON:
{ "source": { "kind": "published", "slug": "gifgrep", "version": "1.2.3" }, "update": false}Notes:
- Scan request payloads and downloadable reports expire from the scan-request store after the retention window.
- Published scans require owner/publisher management access, or platform moderator/admin authority.
- Published scans write back only when
update: trueand the scan completes successfully. - 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 are asynchronous. Manual scan requests are prioritized ahead of normal publish/backfill work, but completion still depends on worker availability.
GET /api/v1/skills/-/scan/{scanId}
Authenticated poll endpoint for a submitted scan.
- Returns queued/running/succeeded/failed status.
- Returns
queue.queuedAheadandqueue.positionwhile queued so clients can show how many prioritized manual scans are ahead of the request. Very large queues are bounded and reported withqueuedAheadIsEstimate: true. - When available,
reportcontainsclawscan,skillspector,staticAnalysis, andvirustotalsections. - Failed scan jobs return
status: "failed"withlastError.
GET /api/v1/skills/-/scan/{scanId}/download
Authenticated report archive endpoint.
- Requires a succeeded scan; non-terminal scans return
409. - Returns a ZIP with
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 stored report archive endpoint for submitted versions.
- Requires owner/publisher management access to the skill or plugin, or platform moderator/admin authority.
- Returns stored scan results for the exact submitted version, including blocked or hidden versions.
kinddefaults toskill; usekind=pluginfor plugin/package scans.- Returns the same ZIP shape as scan-request downloads.
POST /api/v1/skills/-/scan/batch
Admin-only canonical batch rescan route. It accepts the same payload shape as legacy POST /api/v1/skills/-/rescan-batch.
POST /api/v1/skills/-/scan/batch/status
Admin-only canonical batch status route. It accepts { "jobIds": ["..."] } and returns the same aggregate counters as legacy POST /api/v1/skills/-/rescan-batch/status.
GET /api/v1/skills/{slug}/verify
Returns the Skill Card verification envelope used by clawhub skill verify.
Query params:
version(optional): specific version string.tag(optional): resolve a tagged version (for examplelatest).
Notes:
okistrueonly when the selected version has a generated Skill Card, is not malware-blocked by moderation, and ClawScan verification is clean.- Skill identity, publisher identity, and selected version metadata 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 key offok,decision,reasons, andsecurity.status.security.signalscontains supporting scanner evidence such asstaticScan,virusTotal, andskillSpector.security.signals.dependencyRegistryis retained for v1 response compatibility, but the dependency registry existence scanner is retired and this key is alwaysnull.provenanceisserver-resolved-github-importonly when ClawHub resolved and stored a GitHub repo/ref/commit/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 intended for clients that already know which installed ClawHub skill versions they need to display, such as OpenClaw Control UI.
Request:
{ "items": [{ "slug": "gifgrep", "version": "1.2.3" }]}Notes:
itemsmust contain 1-100 unique{ slug, version }pairs.- Results are per item; one missing skill or version does not fail the whole response.
- The response is 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 retained for v1 response compatibility, but the dependency registry existence scanner is retired and this key is alwaysnull.- Skill Card absence 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/gifgrep", "securityAuditUrl": "https://clawhub.ai/steipete/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
Returns raw text content.
Query params:
path(required)version(optional)tag(optional)
Notes:
- Defaults to latest version.
- File size limit: 200KB.
GET /api/v1/packages
Unified catalog endpoint for:
- skills
- code plugins
- bundle plugins
Query params:
limit(optional): integer (1β100)cursor(optional): pagination cursorfamily(optional):skill,code-plugin, orbundle-pluginchannel(optional):official,community, orprivateisOfficial(optional):trueorfalseexecutesCode(optional):trueorfalsecapabilityTag(optional): capability filter for plugin packagescategory(optional): plugin category filter. Supported only when the request is scoped to plugin packages (/api/v1/plugins,/api/v1/code-plugins,/api/v1/bundle-plugins, or package endpoints withfamily=code-plugin/family=bundle-plugin).target/hostTarget(optional): shorthand forhost:<target>os,arch,libc(optional): shorthand for host capability filtersrequiresBrowser,requiresDesktop,requiresNativeDeps,requiresExternalService,requiresBinary,requiresOsPermission(optional):true/1shorthand for environment requirement tagsexternalService,binary,osPermission(optional): shorthand for named environment requirement tagsartifactKind(optional):legacy-zipornpm-packnpmMirror(optional):true/1to show ClawPack-backed package versions available through the npm mirror
Notes:
- Invalid values for
family,channel,isOfficial,executesCode,featured,highlightedOnly,artifactKind, or boolean capability shorthands return400. Unknown query parameters are ignored. GET /api/v1/code-pluginsandGET /api/v1/bundle-pluginsremain fixed-family aliases.- Skill entries stay backed by the skill registry and can still be published only through
POST /api/v1/skills. POST /api/v1/packagesis still only for code-plugin and bundle-plugin releases.- Anonymous callers only see public package channels.
- Authenticated callers can see private packages for publishers they belong to in list/search results.
channel=privateonly returns packages the authenticated caller can read.
GET /api/v1/packages/search
Unified catalog search across skills + plugin packages.
Query params:
q(required): query stringlimit(optional): integer (1β100)family(optional):skill,code-plugin, orbundle-pluginchannel(optional):official,community, orprivateisOfficial(optional):trueorfalseexecutesCode(optional):trueorfalsecapabilityTag(optional): capability filter for plugin packagescategory(optional): plugin category filter. Supported only when the request is scoped to plugin packages.target/hostTarget,os,arch,libc,requiresBrowser,requiresDesktop,requiresNativeDeps,requiresExternalService,requiresBinary,requiresOsPermission,externalService,binary, andosPermissionare accepted as shorthands for common capability tagsartifactKind(optional):legacy-zipornpm-packnpmMirror(optional):true/1to search ClawPack-backed package versions available through the npm mirror
Notes:
- Invalid values for
family,channel,isOfficial,executesCode,featured,highlightedOnly,artifactKind, or boolean capability shorthands return400. Unknown query parameters are ignored. - Anonymous callers only see public package channels.
- Authenticated callers can search private packages for publishers they belong to.
channel=privateonly returns packages the authenticated caller can read.- Artifact filters are backed by indexed capability tags:
artifact:legacy-zip,artifact:npm-pack, andnpm-mirror:available.
GET /api/v1/plugins
Plugin-only catalog browse across code-plugin and bundle-plugin packages.
Query params:
limit(optional): integer (1-100)cursor(optional): pagination cursorisOfficial(optional):trueorfalseexecutesCode(optional):trueorfalsecapabilityTag(optional): capability filter for plugin packagescategory(optional): plugin category filter. Current values:channels,mcp-tooling,data,security,observability,automation,deployment,dev-tools.
GET /api/v1/plugins/export
Bulk export of latest public plugin releases for offline analysis.
Auth:
- API token required.
Query params:
startDate(required): Unix milliseconds lower bound for pluginupdatedAt.endDate(required): Unix milliseconds upper bound for pluginupdatedAt.limit(optional): integer (1-250), default250.cursor(optional): pagination cursor from the previous response.family(optional):code-pluginorbundle-plugin. Omitted means both plugin families.
Response:
- Body: ZIP archive.
- Each exported plugin is rooted at
{family}/{packageName}/. - Each exported plugin includes the latest release's stored files.
- Per-plugin export metadata is stored at
__clawhub_export/{family}/{packageName}/plugin_meta.json. _manifest.jsonis always included at the ZIP root._errors.jsonis included when individual plugins or files could not be exported.
Headers:
X-Next-CursorX-Has-MoreX-Total-ReturnedX-Date-RangeX-Export-Errors
GET /api/v1/plugins/search
Plugin-only search across code-plugin and bundle-plugin packages.
Query params:
q(required): query stringlimit(optional): integer (1-100)isOfficial(optional):trueorfalseexecutesCode(optional):trueorfalsecapabilityTag(optional): capability filter for plugin packagescategory(optional): plugin category filter. Current values:channels,mcp-tooling,data,security,observability,automation,deployment,dev-tools.
Notes:
- Category filtering is a real API filter backed by plugin category digest rows, not a search-query rewrite.
- Results are returned in relevance order and do not currently paginate.
- Browser UI sort controls for plugin search reorder the loaded relevance results,
matching the current
/skillsbrowse behavior.
GET /api/v1/packages/{name}
Returns package detail metadata.
Notes:
- Skills can also resolve through this route in the unified catalog.
- Private packages return
404unless the caller can read the owning publisher.
DELETE /api/v1/packages/{name}
Soft-deletes a package and all 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 one package version, including file metadata, compatibility, capabilities, 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.sha256hash,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.familyidentify the resolved registry package.release.releaseId,release.version, andrelease.createdAtidentify the exact release that was evaluated.release.artifactKind,release.artifactSha256,release.npmIntegrity,release.npmShasum, andrelease.npmTarballNameare present when known for the release artifact.trust.scanStatusis the effective trust status derived from scanner inputs and manual release moderation.trust.moderationStateis nullable. It isnullwhen no manual release moderation exists.trust.blockedFromDownloadis the install block signal. OpenClaw and other install clients should block installation when this value istrueinstead of re-deriving blocking rules from scanner or moderation fields.trust.reasonsis the user-facing and audit explanation list. Reason codes are stable, compact strings such asmanual:quarantined,scan:malicious, andpackage:malicious.trust.pendingmeans one or more trust inputs are still awaiting completion.trust.stalemeans the trust summary was computed from outdated inputs and should be treated as requiring refresh before a high-confidence allow decision.
Notes:
- This endpoint is version-exact. Clients should call it after resolving the package version they intend to install, not just after reading the latest package metadata.
- Private packages return
404unless the caller can read the owning publisher. - This endpoint is intentionally narrower than owner/moderator moderation endpoints. It exposes the install decision and public explanation, not reporter identities, report bodies, private evidence, or internal review timelines.
GET /api/v1/packages/{name}/versions/{version}/artifact
Returns the explicit artifact resolver metadata for a package version.
Notes:
- Legacy package versions return a
legacy-zipartifact and a legacy ZIPdownloadUrl. - ClawPack versions return an
npm-packartifact, npm integrity fields, atarballUrl, and the legacy ZIP compatibility URL. - This is the OpenClaw resolver surface; it avoids guessing archive format from a shared URL.
GET /api/v1/packages/{name}/versions/{version}/artifact/download
Downloads the version artifact through 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=. - Uses the download rate bucket.
GET /api/v1/packages/{name}/readiness
Returns computed readiness for future OpenClaw consumption.
Readiness checks cover:
- official channel status
- latest version availability
- ClawPack npm-pack artifact availability
- artifact digest
- source repo 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 rows.
Auth:
- Requires an API token for 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 (1-100)cursor(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 row.
Auth:
- Requires an API token for 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 is the stable upsert key.packageNameis npm-name normalized; the package can be missing for planned migrations.- This tracks migration readiness only. It does not mutate OpenClaw or generate ClawPacks.
GET /api/v1/packages/moderation/queue
Moderator/admin endpoint for package release review queues.
Auth:
- Requires an API token for a moderator or admin user.
Query params:
status(optional):open(default),blocked,manual, oralllimit(optional): integer (1-100)cursor(optional): pagination cursor
Status meanings:
open: suspicious, malicious, pending, quarantined, revoked, or reported releases.blocked: quarantined, revoked, or malicious releases.manual: any release with a manual moderation override.all: any release with a manual override, non-clean scan state, or package report.
Response:
{ "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
Report a package for moderator review. Reports are package-level, optionally linked to a version. They feed the moderation queue but do not auto-hide or block downloads by themselves; moderators should use release moderation to approve, quarantine, or revoke artifacts.
Auth:
- Requires an API token.
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/admin endpoint for package report intake.
Auth:
- Requires an API token for a moderator or admin user.
Query params:
status(optional):open(default),confirmed,dismissed, oralllimit(optional): integer (1-100)cursor(optional): pagination cursor
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
Owner/moderator endpoint for package moderation visibility.
Auth:
- Requires an API token for the package owner, publisher member, moderator, or 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/admin endpoint for resolving or reopening package reports.
Request:
{ "status": "confirmed", "note": "Reviewed and quarantined affected release.", "finalAction": "quarantine"}note is required for confirmed and dismissed; it may be omitted when
setting status back to open. Pass finalAction: "quarantine" or
finalAction: "revoke" with a confirmed report to apply release moderation in the
same auditable workflow.
Response:
{ "ok": true, "reportId": "packageReports:...", "packageId": "packages:...", "status": "confirmed", "reportCount": 0}POST /api/v1/packages/{name}/versions/{version}/moderation
Moderator/admin endpoint for package release review.
Request:
{ "state": "quarantined", "reason": "Suspicious native payload." }Supported states:
approved: manually reviewed and allowed.quarantined: blocked pending follow-up.revoked: blocked after a release was previously trusted.
Quarantined and revoked releases return 403 from artifact download routes.
Every change writes an audit log entry.
GET /api/v1/packages/{name}/file
Returns raw text content for a package file.
Query params:
path(required)version(optional)tag(optional)
Notes:
- Defaults to the latest release.
- Uses the read rate bucket, not the download bucket.
- Binary files return
415. - File size limit: 200KB.
- 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 latest release.
- Skills redirect to
GET /api/v1/download. - Plugin/package archives are zip files with a
package/root so old OpenClaw clients keep working. - This route stays ZIP-only. It does not stream ClawPack
.tgzfiles. - 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 ClawPack-backed package versions.
Notes:
- Only versions with uploaded ClawPack npm-pack tarballs are listed.
- Legacy ZIP-only versions are intentionally omitted.
dist.tarball,dist.integrity, anddist.shasumuse npm-compatible fields so users can point npm at the mirror if they choose.- Scoped package packuments support both
/api/npm/@scope/nameand npm's encoded/api/npm/@scope%2Fnamerequest path.
GET /api/npm/{package}/-/{tarball}.tgz
Streams the exact uploaded ClawPack tarball bytes for npm mirror clients.
Notes:
- Uses the download rate bucket.
- Download headers include ClawHub SHA-256 plus npm integrity/shasum metadata.
- Moderation and private package access checks still apply.
GET /api/v1/resolve
Used by the CLI to map a local fingerprint to a known version.
Query params:
slug(required)hash(required): 64-char hex sha256 of the bundle fingerprint
Response:
{ "slug": "gifgrep", "match": { "version": "1.2.2" }, "latestVersion": { "version": "1.2.3" } }GET /api/v1/download
Downloads a zip of a skill version.
Query params:
slug(required)version(optional): semver stringtag(optional): tag name (e.g.latest)
Notes:
- If neither
versionnortagis provided, the latest version is used. - Soft-deleted versions return
410. - Download stats are counted as unique identities per hour (
userIdwhen API token is valid, otherwise IP).
Auth endpoints (Bearer token)
All endpoints require:
Authorization: Bearer clh_...GET /api/v1/whoami
Validates token and returns the user handle.
POST /api/v1/skills
Publishes a new version.
- Preferred:
multipart/form-datawithpayloadJSON +files[]blobs. - JSON body with
files(storageId-based) is also accepted. - Optional payload field:
ownerHandle. When present, the API resolves that publisher server-side and requires the actor to have publisher access. - Optional payload field:
migrateOwner. WhentruewithownerHandle, an existing skill may move to that owner if the actor is an admin/owner on both the current and target publishers. Without this opt-in, owner changes are rejected.
POST /api/v1/packages
Publishes a code-plugin or bundle-plugin release.
- Requires Bearer token auth.
- Requires
multipart/form-data. - Allowed form fields are
payload, repeatedfilesblobs, or oneclawpacktarball reference.clawpackmay be a.tgzblob or a storage id returned by the upload-url flow. Staged storage-id publishes must also include theclawpackUploadTicketreturned with that upload URL. - Use either
filesorclawpack, never both in the same request. - JSON bodies and caller-supplied
payload.files/payload.artifactmetadata are rejected. - Direct multipart publish requests are capped at 18MB. ClawPack tarballs may use the upload-url flow up to the 120MB tarball cap.
- Optional payload field:
ownerHandle. When present, only admins may publish on behalf of that owner.
Validation highlights:
familymust becode-pluginorbundle-plugin.- Plugin packages require
openclaw.plugin.json. ClawPack.tgzuploads must contain it 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 optional metadata.- Only the
openclaworg publisher and currentopenclaworg members' personal publishers may publish to theofficialchannel. - On-behalf publishes still validate official-channel eligibility against the target owner account.
DELETE /api/v1/skills/{slug} / POST /api/v1/skills/{slug}/undelete
Soft-delete / restore a skill (owner, moderator, or admin).
Optional JSON body:
{ "reason": "Held for moderation pending legal review." }When present, reason is stored as the skill moderation note and copied into the audit log.
Owner-initiated soft deletes reserve the slug for 30 days, then the slug can be claimed by
another publisher. The delete response includes slugReservedUntil when this expiry applies.
Moderator/admin hides and security removals do not expire this way.
Delete response:
{ "ok": true, "slugReservedUntil": 1730000000000 }Status codes:
200: ok401: unauthorized403: forbidden404: skill/user not found500: internal server error
POST /api/v1/users/publisher
Admin-only. Ensures an org publisher exists for a handle. If the handle still points at a
legacy shared user/personal publisher, the endpoint migrates it into an org publisher first.
For a newly-created org, provide 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-serve org publisher creation. Creates a new org publisher and adds the caller as owner. This endpoint does not migrate existing user/personal handles and does not mark the publisher trusted/official.
- Body:
{ "handle": "opik", "displayName": "Opik" } - Response:
{ "ok": true, "publisherId": "...", "handle": "opik", "created": true, "trusted": false } - Returns
409when the handle is already used by a publisher, user, or personal publisher.
POST /api/v1/users/reserve
Admin-only. Reserves root slugs and package names for a rightful owner without publishing a release. Package names become private placeholder packages with no release rows, so the same owner can later publish the real 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" }] }
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 auth and only work for the skill owner.
renamepreserves the previous slug as a redirect alias.mergehides 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
Ban a user and hard-delete owned skills (moderator/admin only).
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
Unban a user and restore 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
Change the stored reason for an existing ban without unbanning or restoring
content (admin only). Defaults to dry-run unless dryRun is 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
Change a user role (admin only).
Body:
{ "handle": "user_handle", "role": "moderator" }or
{ "userId": "users_...", "role": "admin" }Response:
{ "ok": true, "role": "moderator" }GET /api/v1/users
List or search users (admin only).
Query params:
q(optional): search queryquery(optional): alias forqlimit(optional): max results (default 20, max 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}
Add/remove a star (highlights). Both endpoints are idempotent.
Responses:
{ "ok": true, "starred": true, "alreadyStarred": false }{ "ok": true, "unstarred": true, "alreadyUnstarred": false }Legacy CLI endpoints (deprecated)
Still supported for 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
See DEPRECATIONS.md for removal plan.
POST /api/cli/upload-url returns uploadUrl and uploadTicket. Package
publishes that stage a ClawPack tarball must send the resulting storage id as
clawpack and the returned ticket as clawpackUploadTicket.
Registry discovery (/.well-known/clawhub.json)
The CLI can discover registry/auth settings from the site:
/.well-known/clawhub.json(JSON, preferred)/.well-known/clawdhub.json(legacy)
Schema:
{ "apiBase": "https://clawhub.ai", "authBase": "https://clawhub.ai", "minCliVersion": "0.0.5" }If you self-host, serve this file (or set CLAWHUB_REGISTRY explicitly; legacy CLAWDHUB_REGISTRY).
