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 Unauthorized when 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: 0 and RateLimit-Remaining: 0
  • On 429: Retry-After

What the headers mean:

  • X-RateLimit-Reset: absolute Unix epoch seconds
  • RateLimit-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) on 429

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-After exists, wait that many seconds before retry.
  • Use jittered backoff to avoid synchronized retries.
  • If Retry-After is missing, fallback to RateLimit-Reset (or compute from X-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 string
  • limit (optional): integer
  • mode (optional): exact for deterministic exact-slug matches
  • highlightedOnly (optional): true to filter to highlighted skills
  • nonSuspiciousOnly (optional): true to hide suspicious (flagged.suspicious) skills
  • nonSuspicious (optional): legacy alias for nonSuspiciousOnly

Search modes:

  • Leave out mode when you want the standard relevance-based skill search.
  • Passing mode=exact makes q match as an exact skill slug, skipping the native semantic/vector lookup.
  • Supplying an invalid mode value results in 400 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-map carries a lone map token, whereas amap-jsapi-skill yields amap, jsapi, and skill; a query for map thus gives personal-map a stronger lexical match than amap-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/slug while 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 than trending
  • sort (optional): updated (default), recommended (alias: default), createdAt (alias: newest), downloads, stars (alias: rating), name, legacy install aliases installsCurrent/installs/installsAllTime resolve to downloads, trending
  • prefix (optional): literal skill-slug prefix; results come back in ascending slug order, and sort=name is mandatory when sort is present
  • nonSuspiciousOnly (optional): true hides suspicious (flagged.suspicious) skills
  • nonSuspicious (optional): legacy alias for nonSuspiciousOnly

Bad sort values produce 400.

Notes:

  • recommended draws on engagement and recency signals.
  • trending orders by installs from the past 7 days, based on telemetry.
  • createdAt stays stable for crawling newly added skills; updated shifts when existing skills get republished.
  • Prefix listing remains complete across pages: keep following nextCursor until it equals null.
  • With nonSuspiciousOnly=true, cursor-based sorts may deliver fewer than limit items per page, since suspicious skills get filtered out after the page is fetched.
  • When nextCursor appears, 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, null applies.
  • metadata.systems: Nix system targets (for example ["aarch64-darwin", "x86_64-linux"]). When missing, null is used.
  • metadata becomes null whenever the skill carries no platform metadata.
  • moderation appears 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 200 is 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, or all
  • limit (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): integer
  • cursor (optional): pagination cursor

GET /api/v1/skills/{slug}/versions/{version}

Returns version metadata plus a file listing.

  • When available, version.security carries 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 instance latest).

Notes:

  • If both version and tag are omitted, the latest version is used.
  • Normalized verification status is included alongside scanner-specific details.
  • security.hasScanResult equals true only when a scanner delivered a definitive verdict (clean, suspicious, or malicious).
  • moderation reflects a current skill-level moderation snapshot built from the newest version.
  • When you query a historical version, review moderation.matchesRequestedVersion and moderation.sourceVersion before assuming moderation and security refer 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: true is set and the scan finishes without errors.
  • The response comes back as 202 with { "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.queuedAhead and queue.position, letting clients display how many prioritized manual scans sit ahead of this one. Extremely large queues are capped and indicated via queuedAheadIsEstimate: true.
  • When data is ready, report holds sections for clawscan, skillspector, staticAnalysis, and virustotal.
  • A failed scan job yields status: "failed" along with lastError.

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, and README.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.
  • kind is set to skill by default; switch to kind=plugin for 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 instance latest).

Notes:

  • ownerHandle gets normalized through trimming whitespace, stripping any leading @ characters, and converting everything to lowercase.
  • ok is set to true only 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.
  • security serves as the top-level ClawScan/security outcome. Automation should rely on ok, decision, reasons, and security.status.
  • security.signals carries supporting scanner evidence, including staticScan, virusTotal, and skillSpector.
  • For backward compatibility with v1 responses, security.signals.dependencyRegistry is kept, but the dependency registry existence scanner is no longer active, and this key always holds null.
  • provenance becomes server-resolved-github-import only when ClawHub successfully resolved and stored a GitHub repo/ref/commit/path during publish or import; otherwise it holds unavailable.

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:

  • ownerHandle is 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.
  • items needs 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.signals holds status-level supporting evidence only; for complete scanner details, use /scan or the ClawHub security-audit page.
  • To stay compatible with v1 responses, security.signals.dependencyRegistry is preserved, but the dependency registry existence scanner is retired, so this key always returns null.
  • The absence of a Skill Card does not change this endpoint's ok, decision, or reasons; clients that need card content should read installed skill-card.md locally.
  • Reach for /verify when you want the single-skill Skill Card verification envelope, /card for generated card markdown, and /scan for 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; yields text/plain or 415 when 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 100
  • cursor (optional): cursor for pagination
  • family (optional): one of skill, code-plugin, or bundle-plugin
  • channel (optional): choose from official, community, or private
  • isOfficial (optional): either true or false
  • sort (optional): updated by default, with recommended, trending, downloads, and the legacy alias installs as alternatives
  • category (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 using family=code-plugin/family=bundle-plugin. The controlled categories and legacy v1 filter aliases are detailed in GET /api/v1/plugins.

Notes:

  • Supplying invalid values for family, channel, isOfficial, featured, highlightedOnly, or sort results in 400. Any unknown query parameters are simply disregarded.
  • Both GET /api/v1/code-plugins and GET /api/v1/bundle-plugins continue 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/packages remains 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=private restricts 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 text
  • limit (optional): an integer from 1 to 100
  • family (optional): one of skill, code-plugin, or bundle-plugin
  • channel (optional): choose from official, community, or private
  • isOfficial (optional): either true or false
  • category (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 in GET /api/v1/plugins.

Notes:

  • Supplying an invalid value for family, channel, isOfficial, featured, or highlightedOnly yields 400. 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=private restricts 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 100
  • cursor (optional): cursor for pagination
  • isOfficial (optional): either true or false
  • sort (optional): recommended by default, or trending, downloads, updated, with legacy alias installs
  • category (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, and automation map to tools.
  • Both observability and deployment map to gateway.
  • dev-tools maps to runtime.

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 skill updatedAt.
  • endDate (required): Unix milliseconds, the upper bound for skill updatedAt.
  • limit (optional): integer, 1 to 250, with a default of 250.
  • 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.json alongside sourceRef: "public-clawhub".
  • For GitHub-backed skills that currently have a clean or suspicious scan, the export includes _source_handoff.json with sourceRef: "public-github", repo, commit, path, content hash, and archive URL. ClawHub-hosted source files are left out.
  • _export_skill_meta.json is 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.json is added. Those same pre-stream errors are surfaced by X-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.json or X-Export-Errors misrepresents its contents.

Headers:

  • X-Next-Cursor
  • X-Has-More
  • X-Total-Returned
  • X-Date-Range
  • X-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 plugin updatedAt.
  • endDate (required): Unix milliseconds upper bound for plugin updatedAt.
  • limit (optional): integer (1-250), defaults to 250.
  • cursor (optional): pagination cursor taken from the prior response.
  • family (optional): code-plugin or bundle-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.json appears when individual plugins or files could not be exported.

Headers:

  • X-Next-Cursor
  • X-Has-More
  • X-Total-Returned
  • X-Date-Range
  • X-Export-Errors

GET /api/v1/plugins/search

Plugin-only search that covers both code-plugin and bundle-plugin packages.

Query params:

  • q (required): query string
  • limit (optional): integer (1-100)
  • isOfficial (optional): true or false
  • category (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/plugins remain 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 /skills browse 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, 404 is 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 100
  • cursor (optional): a cursor for pagination

Important points:

  • For private packages, 404 is 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.kind is set to legacy-zip for legacy package archives, or to npm-pack for releases backed by ClawPack.
  • Releases from ClawPack come with npm-compatible npmIntegrity, npmShasum, and npmTarballName fields.
  • The version.sha256hash field holds deprecated compatibility metadata intended for older clients. It hashes the exact ZIP bytes that /api/v1/packages/{name}/download returns. For modern clients, version.artifact.sha256 is the recommended field, as it points to the canonical release artifact.
  • When scan data exists, version.vtAnalysis, version.llmAnalysis, and version.staticScan are 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, and package.family.
  • The specific release that was assessed is identified by release.releaseId, release.version, and release.createdAt.
  • When available for the release artifact, release.artifactKind, release.artifactSha256, release.npmIntegrity, release.npmShasum, and release.npmTarballName are present.
  • trust.scanStatus reflects the effective trust status, which is derived from scanner inputs and manual release moderation.
  • trust.moderationState can be null. When no manual release moderation exists, it is null.
  • The install block signal is trust.blockedFromDownload. OpenClaw and other install clients should halt installation when this value is true, 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 like manual:quarantined, scan:malicious, and package:malicious.
  • When one or more trust inputs are still pending completion, trust.pending is indicated.
  • trust.stale signals 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, 404 is 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-zip artifact along with a legacy ZIP downloadUrl.
  • ClawPack versions provide an npm-pack artifact, npm integrity fields, a tarballUrl, 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 .tgz bytes.
  • 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, or all (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.
  • packageName follows 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, or all
  • limit (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, or all
  • limit (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; returns text/plain or 415 when 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 415 only for preview requests.
  • Reads are not blocked by pending VirusTotal scans; malicious releases may still be withheld elsewhere.
  • Private packages return 404 unless 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 .tgz files.
  • Responses include ETag, Digest, X-ClawHub-Artifact-Type, and X-ClawHub-Artifact-Sha256 headers 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 404 unless 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, and dist.shasum use npm-compatible fields so users can point npm at the mirror if they choose.
  • Scoped package packuments support both /api/npm/@scope/name and npm's encoded /api/npm/@scope%2Fname request 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 string
  • tag (optional): tag name (e.g. latest)

Notes:

  • The latest version is used when neither version nor tag is 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, and archiveUrl; scan/current state is a gate and is not included as success payload metadata.
  • Download stats are counted as unique identities per UTC day (userId when 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-data combined with payload JSON and files[] 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. When true is set with ownerHandle, 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-data is required.
  • Permitted form fields: payload, repeated files blobs, or a single clawpack tarball reference. clawpack can be a .tgz blob or a storage id from the upload-url flow. Staged storage-id publishes must also carry the clawpackUploadTicket that came with that upload URL.
  • Pick either files or clawpack; sending both in one request is invalid.
  • JSON bodies and caller-supplied payload.files / payload.artifact metadata 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:

  • family has to be code-plugin or bundle-plugin.
  • Plugin packages need openclaw.plugin.json. ClawPack .tgz uploads must include it at package/openclaw.plugin.json.
  • Code plugins require package.json, source repo metadata, source commit metadata, config schema metadata, openclaw.compat.pluginApi, and openclaw.build.openclawVersion.
  • openclaw.hostTargets and openclaw.environment are optional metadata.
  • Publishing to the official channel is restricted to the openclaw org publisher and personal publishers of current openclaw org 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: ok
  • 401: unauthorized
  • 403: forbidden
  • 404: skill/user not found
  • 500: 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 409 when 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" }
  • POST /api/v1/skills/{slug}/merge
    • Body: { "targetSlug": "canonical-target-slug" }
    • Response: { "ok": true, "sourceSlug": "old-slug", "targetSlug": "canonical-target-slug" }

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.
  • merge removes 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 }
  • POST /api/v1/skills/{slug}/transfer/accept
  • POST /api/v1/skills/{slug}/transfer/reject
  • POST /api/v1/skills/{slug}/transfer/cancel
    • Response (accept/reject/cancel): { "ok": true, "skillSlug": "demo-skill?" }
  • GET /api/v1/transfers/incoming
  • GET /api/v1/transfers/outgoing
    • Response shape: { "transfers": [{ "_id": "...", "skill": { "slug": "demo", "displayName": "Demo" }, "fromUser"|"toUser": { "handle": "..." }, "message": "...", "requestedAt": 0, "expiresAt": 0 }] }

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 query
  • query (optional): alias for q
  • limit (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/whoami
  • POST /api/cli/upload-url
  • POST /api/cli/publish
  • POST /api/cli/telemetry/install
  • POST /api/cli/skill/delete
  • POST /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.

7,435 words · updated Sep 1, 2026