ClawHub HTTP API Reference: Public Endpoints, Auth, and Rate Limits
Reference for ClawHub's HTTP API, covering public catalog reuse, authentication, and rate limits. Useful for developers integrating with ClawHub or building third-party directories.
Read this when
- Adding/changing endpoints
- Debugging CLI ↔ registry requests
HTTP API
Base URL: https://clawhub.ai (default).
Every v1 route lives under /api/v1/....
Older paths /api/... and /api/cli/... are still available for backward compatibility (see DEPRECATIONS.md).
OpenAPI spec: /api/v1/openapi.json.
Public catalog reuse
Public read endpoints let third-party directories list or search ClawHub skills. Cache your results, respect 429/Retry-After, point users back to the official ClawHub listing (https://clawhub.ai/<owner>/skills/<slug>), and never suggest that ClawHub endorses the third-party site. Do not use the public API to mirror content that is hidden, private, or blocked by moderation.
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
How enforcement works:
-
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-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 until reset (delay)X-RateLimit-Remaining/RateLimit-Remaining: exact remaining budget when present. Sharded successful requests omit this header instead of returning 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
Advice for clients:
- 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:
- Trusted client IP headers, including
cf-connecting-ip, are used only when the deployment explicitly enables trusted forwarded headers. - ClawHub uses trusted forwarding headers to identify client IPs at the edge.
- If no trusted client IP is available, anonymous requests use fallback buckets scoped only 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 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): integermode(optional):exactfor deterministic exact-slug matcheshighlightedOnly(optional):trueto filter to highlighted skillsnonSuspiciousOnly(optional):trueto hide suspicious (flagged.suspicious) skillsnonSuspicious(optional): legacy alias fornonSuspiciousOnly
Search modes:
- Leave out
modewhen you want the standard relevance-based skill search. - Passing
mode=exactmakesqmatch as an exact skill slug, skipping the native semantic/vector lookup. - Supplying an invalid
modevalue results in400 Invalid search mode.
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:
- Ordering follows relevance, which combines embedding similarity, boosts for exact slug or display-name token matches, and a modest popularity prior.
- Relevance outweighs popularity. A clean slug or display-name token match can beat a looser match that has far more engagement.
- ASCII text splits into tokens at word and punctuation boundaries. As an example,
personal-mapcarries a lonemaptoken, whereasamap-jsapi-skillyieldsamap,jsapi, andskill; a query formapthus givespersonal-mapa stronger lexical match thanamap-jsapi-skill. - Popularity uses a log scale with a ceiling. A skill with heavy engagement can still rank lower if the query text matches it only weakly.
- Depending on caller filters and current moderation status, a skill flagged as suspicious or hidden may drop out of public search results.
Guidance for publisher discoverability:
- Place the exact terms users are likely to type into the display name, summary, and tags. Reserve a standalone slug token for cases where it doubles as a stable identity you intend to preserve.
- Avoid renaming a slug purely to satisfy one query unless the replacement is a better long-term canonical name. Old slugs persist as redirect aliases, but the canonical URL, displayed slug, and future search digests all point to the new slug.
- Rename aliases keep old URLs and registry-resolved installs working, yet search ranking relies on the canonical skill metadata once the rename is indexed. Existing statistics remain attached to the skill.
- When a skill seems missing, check its moderation state first with
clawhub inspect @owner/slugwhile authenticated, before tweaking ranking-related metadata.
GET /api/v1/skills
Query params:
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),name, legacy install aliasesinstallsCurrent/installs/installsAllTimeresolve todownloads,trendingprefix(optional): literal skill-slug prefix; results come back in ascending slug order, andsort=nameis mandatory whensortis presentnonSuspiciousOnly(optional):truehides suspicious (flagged.suspicious) skillsnonSuspicious(optional): legacy alias fornonSuspiciousOnly
Bad sort values produce 400.
Notes:
recommendeddraws on engagement and recency signals.trendingorders by installs from the past 7 days, based on telemetry.createdAtstays stable for crawling newly added skills;updatedshifts when existing skills get republished.- Prefix listing remains complete across pages: keep following
nextCursoruntil it equalsnull. - With
nonSuspiciousOnly=true, cursor-based sorts may deliver fewer thanlimititems per page, since suspicious skills get filtered out after the page is fetched. - When
nextCursorappears, use it to continue pagination. A short page alone does not signal 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:
- Slugs from earlier owner rename or merge flows now point to the canonical skill.
metadata.os: OS constraints set in the skill frontmatter (such as["macos"],["linux"]). If absent,nullapplies.metadata.systems: Nix system targets (for example["aarch64-darwin", "x86_64-linux"]). When missing,nullis used.metadatabecomesnullwhenever the skill carries no platform metadata.moderationappears only if the skill is flagged or the owner is currently viewing it.
GET /api/v1/skills/{slug}/moderation
Delivers 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:
- Hidden skill moderation details are accessible to owners and moderators.
- For public callers, only
200is returned, and only for visible skills that are already flagged. - Evidence stays redacted for public callers; owners and moderators see raw snippets only.
POST /api/v1/skills/{slug}/report
Submit a skill for moderator review. Reports apply at the skill level, can optionally tie to a version, and enter the skill report queue.
Auth:
- An API token is mandatory.
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 ingesting skill reports.
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" }
For confirmed and dismissed, note is required; it can be left out when status is set back to open. To hide the skill within the same auditable workflow, pass finalAction: "hide" along with a triaged report.
GET /api/v1/skills/{slug}/versions
Query params:
limit(optional): integercursor(optional): pagination cursor
GET /api/v1/skills/{slug}/versions/{version}
Returns version metadata plus a file listing.
- When available,
version.securitycarries normalized scan verification status and scanner details (VirusTotal + LLM).
GET /api/v1/skills/{slug}/scan
Provides security scan verification details for a skill version.
Query params:
version(optional): specific version string.tag(optional): resolve a tagged version (for instancelatest).
Notes:
- If both
versionandtagare omitted, the latest version is used. - Normalized verification status is included alongside scanner-specific details.
security.hasScanResultequalstrueonly when a scanner delivered a definitive verdict (clean,suspicious, ormalicious).moderationreflects a current skill-level moderation snapshot built from the newest version.- When you query a historical version, review
moderation.matchesRequestedVersionandmoderation.sourceVersionbefore assumingmoderationandsecurityrefer to 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:
- Once the retention window closes, scan request bodies and downloadable reports are purged from the scan-request store.
- To view published scans, you need owner/publisher management access or platform moderator/admin privileges.
- Write-back from published scans happens only when
update: trueis set and the scan finishes without errors. - The response comes back as
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 jump ahead of normal publish/backfill tasks, though worker availability still dictates when they finish.
GET /api/v1/skills/-/scan/{scanId}
An authenticated polling endpoint for scans you have submitted.
- Reports whether the job is queued, running, succeeded, or failed.
- While queued, it returns
queue.queuedAheadandqueue.position, letting clients display how many prioritized manual scans sit ahead of this one. Extremely large queues are capped and indicated viaqueuedAheadIsEstimate: true. - When data is ready,
reportholds sections forclawscan,skillspector,staticAnalysis, andvirustotal. - A failed scan job yields
status: "failed"along withlastError.
GET /api/v1/skills/-/scan/{scanId}/download
An authenticated endpoint for downloading the report archive.
- Only works for scans that succeeded; scans still in progress return
409. - Delivers 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
An authenticated endpoint for retrieving stored report archives for submitted versions.
- Demands owner/publisher management access to the skill or plugin, or platform moderator/admin authority.
- Provides stored scan results for the exact submitted version, including versions that are blocked or hidden.
kindis set toskillby default; switch tokind=pluginfor plugin/package scans.- The ZIP layout matches what scan-request downloads produce.
POST /api/v1/skills/-/scan/batch
An admin-only route for canonical batch rescans. It takes the same payload format as the older POST /api/v1/skills/-/rescan-batch.
POST /api/v1/skills/-/scan/batch/status
An admin-only route for canonical batch status. It takes { "jobIds": ["..."] } and returns the same aggregate counters as the older POST /api/v1/skills/-/rescan-batch/status.
GET /api/v1/skills/{slug}/verify
Provides the Skill Card verification envelope that clawhub skill verify relies on.
Query parameters:
ownerHandle(optional): publisher handle for owner-qualified resolution. Use it when several publishers share the same slug.version(optional): a specific version string.tag(optional): resolve a tagged version (for instancelatest).
Notes:
ownerHandlegets normalized through trimming whitespace, stripping any leading@characters, and converting everything to lowercase.okis set totrueonly under these conditions: the chosen version has a generated Skill Card, moderation has not flagged it as malware, and ClawScan verification reports no issues.- Skill identity, publisher identity, and the metadata for the selected version all sit at the top level of the envelope (
slug,displayName,publisherHandle,version,resolvedFrom,tag,createdAt), so shell scripts can read them directly without unwrapping nested structures. securityserves as the top-level ClawScan/security outcome. Automation should rely onok,decision,reasons, andsecurity.status.security.signalscarries supporting scanner evidence, includingstaticScan,virusTotal, andskillSpector.- For backward compatibility with v1 responses,
security.signals.dependencyRegistryis kept, but the dependency registry existence scanner is no longer active, and this key always holdsnull. provenancebecomesserver-resolved-github-importonly when ClawHub successfully resolved and stored a GitHub repo/ref/commit/path during publish or import; otherwise it holdsunavailable.
POST /api/v1/skills/-/security-verdicts
Delivers current compact security verdicts for specific skill versions. This collection endpoint targets clients that already know which installed ClawHub skill versions they need to show, like OpenClaw Control UI.
Request:
{
"items": [
{ "slug": "gifgrep", "ownerHandle": "steipete", "version": "1.2.3" },
{ "slug": "gifgrep", "ownerHandle": "another-publisher", "version": "1.2.3" }
]
}
Notes:
ownerHandleis not required. When supplied, it picks that publisher's skill before exact-version resolution; leaving it out keeps the legacy unqualified slug resolution behavior.- Owner handles go through normalization: trimming whitespace, removing leading
@characters, and lowercasing. itemsneeds 1-100 unique{ ownerHandle?, slug, version }combinations. The same slug and version can appear under different owners.- For qualified success and failure items, the normalized owner is echoed as
requestedOwnerHandle; unqualified items leave that field out. - Results are handled per item; a missing skill, owner-qualified skill, or version does not cause the whole response to fail.
- This response covers security only. It excludes Skill Card data, generated card status, artifact file lists, and detailed scanner payloads.
- Successful items carry top-level
overview, the canonical audit-page text built from the ClawScan summary and guidance. Install clients can show this text without reconstructing it from scanner fields. security.signalsholds status-level supporting evidence only; for complete scanner details, use/scanor the ClawHub security-audit page.- To stay compatible with v1 responses,
security.signals.dependencyRegistryis preserved, but the dependency registry existence scanner is retired, so this key always returnsnull. - The absence of a Skill Card does not change this endpoint's
ok,decision, orreasons; clients that need card content should read installedskill-card.mdlocally. - Reach for
/verifywhen you want the single-skill Skill Card verification envelope,/cardfor generated card markdown, and/scanfor detailed scanner data.
Response:
{
"schema": "clawhub.skill.security-verdicts.v1",
"items": [
{
"ok": true,
"decision": "pass",
"reasons": [],
"requestedSlug": "gifgrep",
"requestedOwnerHandle": "steipete",
"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",
"overview": "ClawScan found no material security concerns.\n\nUse least-privileged credentials when configuring this skill.",
"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",
"requestedOwnerHandle": "another-publisher",
"requestedVersion": "1.0.0",
"error": { "code": "version_not_found", "message": "Version not found" },
"security": null
}
]
}
GET /api/v1/skills/{slug}/file
Returns the exact stored file bytes as a download. Add preview=1 to get a bounded escaped-text
preview; any file with valid UTF-8 bytes is previewable, no matter its extension or MIME
metadata.
Query params:
path(required)version(optional)tag(optional)preview=1(optional; yieldstext/plainor415when the bytes are not valid UTF-8)
Notes:
- Defaults to the latest version.
- Raw download cap: 10MB.
- Text preview cap: 200KB.
GET /api/v1/packages
Unified catalog endpoint covering:
- skills
- code plugins
- bundle plugins
Query params:
limit(optional): an integer from 1 to 100cursor(optional): cursor for paginationfamily(optional): one ofskill,code-plugin, orbundle-pluginchannel(optional): choose fromofficial,community, orprivateisOfficial(optional): eithertrueorfalsesort(optional):updatedby default, withrecommended,trending,downloads, and the legacy aliasinstallsas alternativescategory(optional): filters by plugin category. This applies only when the request targets plugin packages, such as/api/v1/plugins,/api/v1/code-plugins,/api/v1/bundle-plugins, or package endpoints usingfamily=code-plugin/family=bundle-plugin. The controlled categories and legacy v1 filter aliases are detailed inGET /api/v1/plugins.
Notes:
- Supplying invalid values for
family,channel,isOfficial,featured,highlightedOnly, orsortresults in400. Any unknown query parameters are simply disregarded. - Both
GET /api/v1/code-pluginsandGET /api/v1/bundle-pluginscontinue to serve as fixed-family aliases. - Skill entries remain tied to the skill registry, and publishing them is still possible only through
POST /api/v1/skills. POST /api/v1/packagesremains exclusive to code-plugin and bundle-plugin releases.- For anonymous users, only public package channels are visible.
- Authenticated users can view private packages belonging to their publishers when browsing lists or performing searches.
channel=privaterestricts results to packages that the authenticated caller has permission to read.
GET /api/v1/packages/search
Search across the entire catalog, covering both skills and plugin packages.
Query parameters:
q(required): the search textlimit(optional): an integer from 1 to 100family(optional): one ofskill,code-plugin, orbundle-pluginchannel(optional): choose fromofficial,community, orprivateisOfficial(optional): eithertrueorfalsecategory(optional): filters by plugin category, but only when the request is scoped to plugin packages. The controlled categories and legacy v1 filter aliases are described inGET /api/v1/plugins.
Notes:
- Supplying an invalid value for
family,channel,isOfficial,featured, orhighlightedOnlyyields400. Query parameters that are not recognized get ignored. - When not authenticated, only public package channels are visible.
- Once authenticated, a caller can look up private packages for any publisher they belong to.
channel=privaterestricts results to packages the authenticated caller has read access to.
GET /api/v1/plugins
Browsing the catalog for plugins only, covering both code-plugin and bundle-plugin packages.
Query parameters:
limit(optional): integer, ranging from 1 to 100cursor(optional): cursor for paginationisOfficial(optional): eithertrueorfalsesort(optional):recommendedby default, ortrending,downloads,updated, with legacy aliasinstallscategory(optional): filters by plugin category. Acceptable values at present:channels,models,memory,context,voice,media,web,tools,runtime,gateway,security,other.
Read endpoints continue to honor legacy v1 filter aliases:
mcp-tooling,data, andautomationmap totools.- Both
observabilityanddeploymentmap togateway. dev-toolsmaps toruntime.
The seven-day install/download leaderboard is what trending represents, and it relies on recent activity rather than cumulative totals.
When accessed through the combined /api/v1/packages endpoint, it applies exclusively to plugins; for the skill catalog, turn to
/api/v1/skills?sort=trending.
Legacy aliases cannot be used as stored or author-declared category values.
GET /api/v1/skills/export
Exports the newest public skills in bulk for offline analysis.
Authentication:
- An API token is mandatory.
Query parameters:
startDate(required): Unix milliseconds, the lower bound for skillupdatedAt.endDate(required): Unix milliseconds, the upper bound for skillupdatedAt.limit(optional): integer, 1 to 250, with a default of250.cursor(optional): pagination cursor taken from the prior response.
Response:
- Body: ZIP archive.
- Every exported skill is anchored at
{publisher}/{slug}/. - Hosted skills carry the newest saved file versions and appear in
_manifest.jsonalongsidesourceRef: "public-clawhub". - For GitHub-backed skills that currently have a
cleanorsuspiciousscan, the export includes_source_handoff.jsonwithsourceRef: "public-github", repo, commit, path, content hash, and archive URL. ClawHub-hosted source files are left out. _export_skill_meta.jsonis part of each skill.- Archive entry paths are capped at 900 bytes in their signed JSON encoding;
any file exceeding that limit gets flagged in
_errors.json. - The ZIP root always contains
_manifest.json. - When individual skills or files fail to export before the archive manifest
is sealed,
_errors.jsonis added. Those same pre-stream errors are surfaced byX-Export-Errors. - After streaming begins, each hosted file is tied to the signed archive
manifest through path, size, and SHA-256. If a signed file goes missing or
fails an integrity check, the stream stops and the client must throw away
the partial ZIP and try again; the proxy never delivers a finished archive
whose
_manifest.jsonorX-Export-Errorsmisrepresents its contents.
Headers:
X-Next-CursorX-Has-MoreX-Total-ReturnedX-Date-RangeX-Export-Errors
GET /api/v1/plugins/export
Bulk export of the latest public plugin releases, intended for offline analysis.
Auth:
- API token is mandatory.
Query params:
startDate(required): Unix milliseconds lower bound for pluginupdatedAt.endDate(required): Unix milliseconds upper bound for pluginupdatedAt.limit(optional): integer (1-250), defaults to250.cursor(optional): pagination cursor taken from the prior response.family(optional):code-pluginorbundle-plugin. If omitted, both plugin families are included.
Response:
- Body: ZIP archive.
- Each exported plugin is rooted at
{family}/{packageName}/. - Stored files from the latest release are included for every exported plugin.
- Per-plugin export metadata lives at
__clawhub_export/{family}/{packageName}/plugin_meta.json. - The ZIP root always contains
_manifest.json. _errors.jsonappears 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 that covers both code-plugin and bundle-plugin packages.
Query params:
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 documented under
GET /api/v1/pluginsremain valid. - Category filtering works as a genuine API filter backed by plugin category digest rows, not as a rewrite of the search query.
- Results come back sorted by relevance and are not paginated at this time.
- Browser UI sort controls for plugin search reorder the loaded relevance results,
matching the current
/skillsbrowse behavior.
GET /api/v1/packages/{name}
Provides detailed metadata for a package.
Important points:
- In the unified catalog, skills can also be accessed through this endpoint.
- Unless the caller has read access to the owning publisher,
404is returned for private packages.
DELETE /api/v1/packages/{name}
Performs a soft deletion of a package along with all of its releases.
Important points:
- A token is mandatory, which must belong to the package owner, an org publisher owner or admin, a platform moderator, or a platform admin.
GET /api/v1/packages/{name}/versions
Lists the version history.
Query parameters:
limit(optional): an integer between 1 and 100cursor(optional): a cursor for pagination
Important points:
- For private packages,
404is returned unless the caller can read the owning publisher.
GET /api/v1/packages/{name}/versions/{version}
Fetches a single package version, which includes file metadata, compatibility details, verification data, artifact metadata, and scan information.
Important points:
version.artifact.kindis set tolegacy-zipfor legacy package archives, or tonpm-packfor releases backed by ClawPack.- Releases from ClawPack come with npm-compatible
npmIntegrity,npmShasum, andnpmTarballNamefields. - The
version.sha256hashfield holds deprecated compatibility metadata intended for older clients. It hashes the exact ZIP bytes that/api/v1/packages/{name}/downloadreturns. For modern clients,version.artifact.sha256is the recommended field, as it points to the canonical release artifact. - When scan data exists,
version.vtAnalysis,version.llmAnalysis, andversion.staticScanare included. - Unless the caller can read the owning publisher, private packages yield
404.
GET /api/v1/packages/{name}/versions/{version}/security
Delivers the exact security and trust summary for a package release, which install clients rely on. This is the public OpenClaw consumption surface that determines whether a resolved release is safe to install.
Authentication:
- This is a public read endpoint. No token from an owner, publisher, moderator, or admin is needed.
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:
- The resolved registry package is identified by
package.name,package.displayName, andpackage.family. - The specific release that was assessed is identified by
release.releaseId,release.version, andrelease.createdAt. - When available for the release artifact,
release.artifactKind,release.artifactSha256,release.npmIntegrity,release.npmShasum, andrelease.npmTarballNameare present. trust.scanStatusreflects the effective trust status, which is derived from scanner inputs and manual release moderation.trust.moderationStatecan be null. When no manual release moderation exists, it isnull.- The install block signal is
trust.blockedFromDownload. OpenClaw and other install clients should halt installation when this value istrue, rather than recalculating blocking rules from scanner or moderation fields. - The user-facing and audit explanation list is
trust.reasons. Reason codes are stable, compact strings likemanual:quarantined,scan:malicious, andpackage:malicious. - When one or more trust inputs are still pending completion,
trust.pendingis indicated. trust.stalesignals that the trust summary was generated from stale inputs, so a refresh is needed before a high-confidence allow decision.
Important points:
- This endpoint is exact to the version. Clients should invoke it after resolving the package version they plan to install, not merely after checking the latest package metadata.
- For private packages,
404is returned unless the caller can read the owning publisher. - This endpoint is deliberately more limited than the owner or moderator moderation endpoints. It exposes the install decision and public explanation, but 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.
Important points:
- Legacy package versions provide a
legacy-zipartifact along with a legacy ZIPdownloadUrl. - ClawPack versions provide an
npm-packartifact, npm integrity fields, atarballUrl, and the legacy ZIP compatibility URL. - This is the OpenClaw resolver surface; it avoids inferring archive format from a shared URL.
GET /api/v1/packages/{name}/versions/{version}/artifact/download
Downloads the version artifact via the explicit resolver path.
Important points:
- ClawPack versions stream the exact uploaded npm-pack
.tgzbytes. - Legacy ZIP versions redirect to
/api/v1/packages/{name}/download?version=. - The download rate bucket is used.
GET /api/v1/packages/{name}/readiness
Computes 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 that lists official OpenClaw plugin migration rows.
Authentication:
- A token for a moderator or admin user is required.
Query parameters:
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
This admin route handles both creation and updates for official plugin migration records.
Auth:
- An admin user's API token is mandatory.
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:
- The stable upsert key is
bundledPluginId, which gets lowercased for normalization. packageNamefollows npm-name normalization; planned migrations may reference a package that does not exist yet.- Only migration readiness is tracked here. OpenClaw is never modified, and no ClawPacks are produced.
GET /api/v1/packages/moderation/queue
This endpoint serves moderators and admins for reviewing package release queues.
Auth:
- A moderator or admin user's API token is required.
Query params:
status(optional):open(default),blocked,manual, oralllimit(optional): integer (1-100)cursor(optional): pagination cursor
Status meanings:
open: covers releases flagged as suspicious, malicious, pending, quarantined, revoked, or reported.blocked: covers releases that are quarantined, revoked, or malicious.manual: any release carrying a manual moderation override.all: any release with a manual override, a scan state that is not clean, or a 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
Submit a package for moderator attention. Reports apply at the package level, with an optional version link. They enter the moderation queue, yet they do not independently hide or block downloads; moderators should rely on 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/admin route for ingesting package reports.
Auth:
- A moderator or admin user's API token is required.
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
This owner/moderator endpoint controls package moderation visibility.
Auth:
- The API token must belong to 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/admin route for reopening or resolving package reports.
Request:
{
"status": "confirmed",
"note": "Reviewed and quarantined affected release.",
"finalAction": "quarantine"
}
When the action is confirmed or dismissed, note becomes mandatory; it can be left out when status is returned to open. To apply release moderation within the same auditable workflow, pass finalAction: "quarantine" or finalAction: "revoke" along with a confirmed report.
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: reviewed manually and permitted.quarantined: blocked while awaiting follow-up.revoked: blocked because the release was previously trusted.
Artifact download routes respond with 403 for quarantined and revoked releases. An audit log entry accompanies every change.
GET /api/v1/packages/{name}/file
Returns the exact stored package file bytes for download. Add preview=1 to get the same bounded
UTF-8 text preview that skill files use.
Query parameters:
path(required)version(optional)tag(optional)preview=1(optional; returnstext/plainor415when the bytes are not valid UTF-8)
Notes:
- The latest release is used by default.
- This draws from the read rate bucket, not the download bucket.
- Raw download cap: 10MB.
- Text preview cap: 200KB; opaque files return
415only for preview requests. - Reads are not blocked by pending VirusTotal scans; 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 parameters:
version(optional)tag(optional)
Notes:
- The latest release is the default.
- 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 is 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.
- Downloads are not blocked by pending VirusTotal scans; 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 parameters:
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 hosted skill version ZIP, or returns a GitHub source handoff for a
current GitHub-backed skill with a clean or suspicious scan and no hosted
version.
Query parameters:
slug(required)version(optional): semver stringtag(optional): tag name (e.g.latest)
Notes:
- The latest version is used when neither
versionnortagis provided. - Soft-deleted versions return
410. - Hosted skill versions return a streamed deterministic ZIP with
Content-Disposition: attachment; filename="<slug>-<version>.zip". ClawHub applies moderation, rate limiting, and download metering before streaming. - GitHub-backed skill handoffs do not proxy or mirror bytes. The JSON response
includes
sourceRef: "public-github",repo,commit,path,contentHash, andarchiveUrl; scan/current state is a gate and is not included as success payload metadata. - Download stats are counted as unique identities per UTC day (
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.
- Recommended setup:
multipart/form-datacombined withpayloadJSON andfiles[]blobs. - A JSON body using
files(storageId-based) also works. - Optional payload field:
ownerHandle. If included, the API resolves that publisher server-side and demands the actor hold publisher access. - Optional payload field:
migrateOwner. Whentrueis set withownerHandle, an existing skill can transfer to that owner, provided the actor is an admin/owner on both the current and target publishers. Without this opt-in, ownership changes are refused.
POST /api/v1/packages
Releases a code-plugin or bundle-plugin version.
- Bearer token authentication is mandatory.
multipart/form-datais required.- Permitted form fields:
payload, repeatedfilesblobs, or a singleclawpacktarball reference.clawpackcan be a.tgzblob or a storage id from the upload-url flow. Staged storage-id publishes must also carry theclawpackUploadTicketthat came with that upload URL. - Pick either
filesorclawpack; sending both in one request is invalid. - JSON bodies and caller-supplied
payload.files/payload.artifactmetadata get rejected. - Direct multipart publishes max out at 18MB. ClawPack tarballs can go through the upload-url flow, up to the 120MB tarball ceiling.
- Optional payload field:
ownerHandle. When present, only admins can publish for that owner.
Validation notes:
familyhas to becode-pluginorbundle-plugin.- Plugin packages need
openclaw.plugin.json. ClawPack.tgzuploads must include 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.- Publishing to the
officialchannel is restricted to theopenclaworg publisher and personal publishers of currentopenclaworg members. - On-behalf publishes still check official-channel eligibility against the target owner account.
DELETE /api/v1/skills/{slug} / POST /api/v1/skills/{slug}/undelete
Soft-delete or restore a skill (owner, moderator, or admin).
Optional JSON body:
{ "reason": "Held for moderation pending legal review." }
When included, reason gets saved as the skill moderation note and appears in the audit log.
Owner-triggered soft deletes hold the slug for 30 days; after that, another publisher can claim it.
The delete response includes slugReservedUntil when this expiry applies.
Moderator/admin hides and security removals do not expire in this manner.
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. Guarantees an org publisher exists for a handle. If the handle still refers to a legacy shared user/personal publisher, the endpoint first migrates it to an org publisher. For a brand-new org, supply memberHandle; the acting admin is not added as a member.
memberRole falls back 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 org publisher creation. Sets up a new org publisher and assigns the caller as owner. This endpoint does not migrate existing user/personal handles and does not flag the publisher as trusted/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 a rightful owner without releasing anything. 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 under 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. Restores a personal publisher for a verified replacement GitHub OAuth principal without touching Convex Auth account rows. The request must specify both immutable GitHub provider account ids; mutable handles serve only as an operator-facing check.
The endpoint runs in dry-run mode by default. To actually apply recovery, you must supply dryRun: false and confirmIdentityVerified: true once staff have manually confirmed that both GitHub principals are connected. If the destination user's current personal publisher owns any skills, packages, or GitHub skill sources, the recovery fails closed. Legacy ownerUserId fields for the recovered publisher's skills, skill slug aliases, packages, package inspector warnings, and derived search digest rows are migrated as well, ensuring direct-owner paths align with the new publisher authority. Any active protected-handle reservation on the recovered handle gets reassigned to the replacement user, so later profile synchronization cannot bring back the former user's competing authority. Each primary table is capped at 100 rows per apply transaction; for larger recoveries, use a resumable owner migration first. GitHub skill sources are scoped to the publisher and are marked 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:
- API token authentication is required for both endpoints, and they only operate for the skill owner.
- With
rename, the previous slug stays available as a redirect alias. mergeremoves the source listing from view and points 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
Restricted to moderators and admins, this bans a user and permanently deletes all owned skills.
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
Admins can unban a user and bring back any eligible skills.
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
For admins only, this updates the stored reason on an existing ban without unbanning the user or restoring any content. Unless dryRun is set to false, it defaults to dry-run.
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
Admins can change a user's role.
Body:
{ "handle": "user_handle", "role": "moderator" }
or
{ "userId": "users_...", "role": "admin" }
Response:
{ "ok": true, "role": "moderator" }
GET /api/v1/users
Admins can list or search users.
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 or remove a Bookmark. The older stars route and response field names are kept for backward compatibility. 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 the deprecation timeline.
POST /api/cli/upload-url provides both uploadUrl and uploadTicket as its output. When a package publish stages a ClawPack tarball, the storage id produced must be passed along as clawpack, while the returned ticket goes into clawpackUploadTicket. For GitHub Actions publishes, credentials are split between upload and publish scopes, and the ticket is only accepted by the server when both sets of credentials originate from the same authorization transaction.
Registry discovery (/.well-known/clawhub.json)
Registry and auth settings can be pulled from the site by the CLI:
/.well-known/clawhub.json(JSON, the recommended option)/.well-known/clawdhub.json(older format)
Schema:
{ "apiBase": "https://clawhub.ai", "authBase": "https://clawhub.ai", "minCliVersion": "0.0.5" }
For self-hosted deployments, either serve this file directly or set CLAWHUB_REGISTRY explicitly, with CLAWDHUB_REGISTRY as the fallback.