HTTP API Reference for ClawHub: Public and CLI Endpoints with Authentication

This page details the HTTP API for ClawHub, including public read endpoints, CLI endpoints, and authentication. It covers rate limiting, base URLs, and best practices for third-party integrations.

Read this when

  • Adding/changing endpoints
  • Debugging CLI ↔ registry requests

HTTP API

Default base URL: https://clawhub.ai.

All version 1 paths live under /api/v1/.... For backward compatibility, the legacy /api/... and /api/cli/... endpoints remain available (details in DEPRECATIONS.md). OpenAPI specification: /api/v1/openapi.json.

Public catalog reuse

Public read endpoints allow third party directories to list or search ClawHub skills. Cache results, respect 429 and Retry-After, direct users to the official ClawHub listing (https://clawhub.ai/<owner>/skills/<slug>), and never suggest ClawHub endorses your site. Do not mirror any content that is hidden, private, or blocked by moderation outside the public API.

Web slug shortcuts work across registry families, but API clients should prefer the canonical URLs returned by read endpoints rather than reconstructing route precedence.

Rate limits

How enforcement works:

  • Anonymous requests: rate limited per IP address.

  • Authenticated requests (valid Bearer token): rate limited per user bucket.

  • When the token is missing or invalid, the system falls back to IP based enforcement.

  • Authenticated write endpoints should not respond with a bare Unauthorized when the server knows the cause. Missing tokens, invalid or revoked tokens, and deleted, banned, or disabled accounts should each produce descriptive text so CLI clients can inform users about the block.

  • Read: 3000 per minute per IP, 12000 per minute per key

  • Write: 300 per minute per IP, 3000 per minute per key

  • Download: 1200 per minute per IP, 6000 per minute per key (download endpoints)

Headers:

  • Legacy compatibility: X-RateLimit-Limit, X-RateLimit-Reset
  • Standardized: RateLimit-Limit, RateLimit-Reset
  • On 429: X-RateLimit-Remaining: 0 and RateLimit-Remaining: 0
  • On 429: Retry-After

What the headers mean:

  • X-RateLimit-Reset: absolute Unix epoch seconds
  • RateLimit-Reset: seconds remaining until reset (delay)
  • X-RateLimit-Remaining / RateLimit-Remaining: exact remaining budget when present. Sharded successful requests omit this header instead of providing an approximate global value.
  • Retry-After: seconds to wait before retry (delay) 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

Client guidance:

  • If Retry-After is present, pause for that many seconds before retrying.
  • Use jittered backoff to prevent synchronized retries.
  • When Retry-After is absent, fall back to RateLimit-Reset (or derive it from X-RateLimit-Reset).

IP source:

  • Trusted client IP headers, such as cf-connecting-ip, are used only when the deployment explicitly enables trusted forwarded headers.
  • ClawHub uses trusted forwarding headers at the edge to identify client IPs.
  • Without a trusted client IP, anonymous requests fall back to buckets scoped solely by rate limit kind. These fallback buckets do not include caller supplied paths, slugs, package names, versions, query strings, or other artifact parameters.

Error responses

Public v1 error responses are plain text with content-type: text/plain; charset=utf-8. This covers validation failures (400), missing public resources (404), authentication and permission failures (401/403), rate limits (429), and blocked downloads. Clients should treat the response body as a human readable string. Unknown query parameters are silently ignored for compatibility, but recognized query parameters with invalid values return 400.

Public endpoints (no auth)

GET /api/v1/search

Query parameters:

  • q (required): query string
  • limit (optional): integer
  • highlightedOnly (optional): true to restrict results to highlighted skills
  • nonSuspiciousOnly (optional): true to exclude suspicious (flagged.suspicious) skills
  • nonSuspicious (optional): legacy alias for nonSuspiciousOnly

Response:

{
  "results": [
    {
      "score": 0.123,
      "slug": "gifgrep",
      "displayName": "GifGrep",
      "summary": "…",
      "version": "1.2.3",
      "updatedAt": 1730000000000,
      "ownerHandle": "openclaw",
      "owner": {
        "handle": "openclaw",
        "displayName": "OpenClaw",
        "image": "https://example.com/avatar.png"
      }
    }
  ]
}

Notes:

  • Results are ordered by relevance, which combines embedding similarity with exact boosts for slug and name token matches, plus a small popularity prior.
  • Relevance takes precedence over popularity. A direct match on a slug or display-name token can beat a less precise match even if the latter has significantly higher engagement.
  • ASCII text is split into tokens at word and punctuation boundaries. For instance, personal-map includes the token map by itself, while amap-jsapi-skill contains amap, jsapi, and skill; a search for map gives personal-map a stronger lexical match than amap-jsapi-skill.
  • Popularity uses a log scale with a cap. Skills with high engagement can still rank lower if the query text does not match as closely.
  • Skills in a suspicious or hidden moderation state may be excluded from public search results, depending on the caller's filters and the current moderation status.

Guidance for publisher discoverability:

  • Place the exact terms users will search for in the display name, summary, and tags. Use a standalone slug token only when it also serves as a stable identifier you intend to keep.
  • Do not rename a slug just to match a single query unless the new slug is a better long-term canonical name. Old slugs become redirect aliases, but the canonical URL, displayed slug, and future search digests all use the new slug.
  • Rename aliases keep old URLs and installs that resolve through the registry working, but search ranking is based on the canonical skill metadata after the rename is indexed. Existing statistics remain with the skill.
  • If a skill is unexpectedly missing from results, first check its moderation state using clawhub inspect @owner/slug while logged in, before changing any ranking-related metadata.

GET /api/v1/skills

Query parameters:

  • limit (optional): integer (1, 200)
  • cursor (optional): pagination cursor for any sort other than trending
  • sort (optional): updated (default), recommended (alias: default), createdAt (alias: newest), downloads, stars (alias: rating), legacy install aliases installsCurrent/installs/installsAllTime map to downloads, trending
  • nonSuspiciousOnly (optional): true to hide suspicious (flagged.suspicious) skills
  • nonSuspicious (optional): legacy alias for nonSuspiciousOnly

Invalid sort values return 400.

Notes:

  • recommended uses engagement and recency signals.
  • trending ranks by installs from the last 7 days (based on telemetry).
  • createdAt is stable for crawling new skills; updated changes when existing skills are republished.
  • When nonSuspiciousOnly=true, cursor-based sorts may return fewer than limit items per page because suspicious skills are filtered after the page is retrieved.
  • Use nextCursor to continue pagination when it is present. A short page does not necessarily indicate the end of results.

Response:

{
  "items": [
    {
      "slug": "gifgrep",
      "displayName": "GifGrep",
      "summary": "…",
      "topics": ["Productivity"],
      "tags": { "latest": "1.2.3" },
      "stats": {},
      "createdAt": 0,
      "updatedAt": 0,
      "latestVersion": { "version": "1.2.3", "createdAt": 0, "changelog": "…" },
      "metadata": { "os": ["macos"], "systems": ["aarch64-darwin"] }
    }
  ],
  "nextCursor": null
}

GET /api/v1/skills/{slug}

Response:

{
  "skill": {
    "slug": "gifgrep",
    "displayName": "GifGrep",
    "summary": "…",
    "topics": ["Productivity"],
    "tags": { "latest": "1.2.3" },
    "stats": {},
    "createdAt": 0,
    "updatedAt": 0
  },
  "latestVersion": { "version": "1.2.3", "createdAt": 0, "changelog": "…" },
  "metadata": { "os": ["macos"], "systems": ["aarch64-darwin"] },
  "owner": { "handle": "steipete", "displayName": "Peter", "image": null },
  "moderation": {
    "isSuspicious": false,
    "isMalwareBlocked": false,
    "verdict": "clean",
    "reasonCodes": [],
    "summary": null,
    "engineVersion": "v2.0.0",
    "updatedAt": 0
  }
}

Notes:

  • Old slugs created through owner rename or merge flows resolve to the canonical skill.
  • metadata.os: OS restrictions declared in the skill frontmatter (e.g. ["macos"], ["linux"]). null if not declared.
  • metadata.systems: Nix system targets (e.g. ["aarch64-darwin", "x86_64-linux"]). null if not declared.
  • metadata is null if the skill has no platform metadata.
  • moderation is included only when the skill is flagged or the owner is viewing it.

GET /api/v1/skills/{slug}/moderation

Returns structured moderation state.

Response:

{
  "moderation": {
    "isSuspicious": true,
    "isMalwareBlocked": false,
    "verdict": "suspicious",
    "reasonCodes": ["suspicious.dynamic_code_execution"],
    "summary": "Detected: suspicious.dynamic_code_execution",
    "engineVersion": "v2.0.0",
    "updatedAt": 0,
    "legacyReason": null,
    "evidence": [
      {
        "code": "suspicious.dynamic_code_execution",
        "severity": "critical",
        "file": "index.ts",
        "line": 3,
        "message": "Dynamic code execution detected.",
        "evidence": ""
      }
    ]
  }
}

Notes:

  • Owners and moderators can access moderation details for hidden skills.
  • Public callers only receive 200 for already flagged visible skills.
  • Evidence is redacted for public callers and includes only raw snippets for owners and moderators.

POST /api/v1/skills/{slug}/report

Submit a skill for moderator review. Reports target a specific skill, can optionally reference a version, and feed into the skill report queue.

Authentication:

  • An API token is required.

Request:

{ "reason": "Suspicious install step", "version": "1.2.3" }

Response:

{
  "ok": true,
  "reported": true,
  "alreadyReported": false,
  "reportId": "skillReports:...",
  "skillId": "skills:...",
  "reportCount": 1
}

GET /api/v1/skills/-/reports

Endpoint for moderators and administrators to retrieve skill reports.

Query parameters:

  • status (optional): accepts open (default), confirmed, dismissed, or all
  • limit (optional): an integer between 1 and 200
  • cursor (optional): cursor for pagination

Response:

{
  "items": [
    {
      "reportId": "skillReports:...",
      "skillId": "skills:...",
      "skillVersionId": "skillVersions:...",
      "slug": "gifgrep",
      "displayName": "GifGrep",
      "version": "1.2.3",
      "reason": "Suspicious install step",
      "status": "open",
      "createdAt": 1730000000000,
      "reporter": {
        "userId": "users:...",
        "handle": "reporter",
        "displayName": "Reporter"
      },
      "triagedAt": null,
      "triagedBy": null,
      "triageNote": null
    }
  ],
  "nextCursor": null,
  "done": true
}

POST /api/v1/skills/-/reports/{reportId}/triage

Endpoint for moderators and administrators to resolve or reopen skill reports.

Request body:

{ "status": "confirmed", "note": "Reviewed and hid affected version.", "finalAction": "hide" }

When using confirmed or dismissed, note is mandatory. It can be left out when returning status to open. To hide the skill through the same auditable workflow, pass finalAction: "hide" along with a triaged report.

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

Query parameters:

  • limit (optional): integer
  • cursor (optional): pagination cursor

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

Provides version metadata along with a file listing.

  • When available, version.security contains normalized scan verification status and scanner details (VirusTotal and LLM).

GET /api/v1/skills/{slug}/scan

Provides security scan verification details for a specific skill version.

Query parameters:

  • version (optional): a specific version string.
  • tag (optional): resolves a tagged version, for instance latest.

Notes:

  • If neither version nor tag is supplied, the latest version is used.
  • Includes normalized verification status and scanner-specific details.
  • security.hasScanResult is set to true only when a scanner produced a conclusive verdict (clean, suspicious, or malicious).
  • moderation represents a current moderation snapshot at the skill level, derived from the latest version.
  • When querying a historical version, review moderation.matchesRequestedVersion and moderation.sourceVersion before treating moderation and security as belonging to the same version context.

POST /api/v1/skills/-/scan

Authenticated submission endpoint for creating new ClawScan jobs.

Local upload scans are no longer supported. Requests containing multipart/form-data or { "source": { "kind": "upload" } } result in 410.

Published scans are submitted as JSON:

{
  "source": { "kind": "published", "slug": "gifgrep", "version": "1.2.3" },
  "update": false
}

Notes:

  • Scan request payloads and downloadable reports are removed from the scan-request store once the retention period expires.
  • Published scans require owner or publisher management access, or platform moderator/admin privileges.
  • Published scans write back only when update: true is present and the scan finishes successfully.
  • The response is 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 receive priority over normal publish or backfill work, but completion still depends on worker availability.

GET /api/v1/skills/-/scan/{scanId}

Authenticated polling endpoint for a submitted scan.

  • Returns queued, running, succeeded, or failed status.
  • While queued, returns queue.queuedAhead and queue.position so clients can see how many prioritized manual scans are ahead of the request. Very large queues are capped and reported using queuedAheadIsEstimate: true.
  • When available, report includes clawscan, skillspector, staticAnalysis, and virustotal sections.
  • Failed scan jobs return status: "failed" with lastError.

GET /api/v1/skills/-/scan/{scanId}/download

Authenticated endpoint for archiving reports.

  • A scan must have succeeded; scans that are not terminal return 409.
  • The response is a ZIP containing manifest.json, clawscan.json, skillspector.json, static-analysis.json, virustotal.json, and README.md.

GET /api/v1/skills/-/scan/download/{name}?version=<version>&kind=skill|plugin

Authenticated endpoint for downloading stored report archives of submitted versions.

  • Requires owner or publisher management access to the skill or plugin, or platform moderator or admin privileges.
  • Returns stored scan results for the exact version that was submitted, including versions that are blocked or hidden.
  • kind has a default of skill; for plugin or package scans, use kind=plugin.
  • The ZIP structure matches what scan-request downloads produce.

POST /api/v1/skills/-/scan/batch

Admin-only route for canonical batch rescans. It uses the same payload structure as the legacy POST /api/v1/skills/-/rescan-batch.

POST /api/v1/skills/-/scan/batch/status

Admin-only route for canonical batch status. It accepts { "jobIds": ["..."] } and provides the same aggregate counters as the legacy POST /api/v1/skills/-/rescan-batch/status.

GET /api/v1/skills/{slug}/verify

Returns the Skill Card verification envelope that clawhub skill verify uses.

Query parameters:

  • version (optional): a specific version string.
  • tag (optional): resolves a tagged version, for example latest.

Notes:

  • ok is true only when the chosen version has a generated Skill Card, is not blocked by moderation for malware, and ClawScan verification passes cleanly.
  • Skill identity, publisher identity, and metadata for the selected version are top-level envelope fields (slug, displayName, publisherHandle, version, resolvedFrom, tag, createdAt), so shell automation can read them without unpacking nested wrappers.
  • security is the top-level ClawScan security verdict. Automation should rely on ok, decision, reasons, and security.status.
  • security.signals contains supporting scanner evidence like staticScan, virusTotal, and skillSpector.
  • security.signals.dependencyRegistry is kept for compatibility with v1 responses, but the dependency registry existence scanner has been retired and this key is always null.
  • provenance is server-resolved-github-import only when ClawHub resolved and stored a GitHub repo, ref, commit, or path during publish or import; otherwise it is unavailable.

POST /api/v1/skills/-/security-verdicts

Returns current compact security verdicts for exact skill versions. This collection endpoint is meant for clients that already know which installed ClawHub skill versions they need to show, for example the OpenClaw Control UI.

Request:

{
  "items": [{ "slug": "gifgrep", "version": "1.2.3" }]
}

Notes:

  • items must contain 1 to 100 unique { slug, version } pairs.
  • Results are per item; a single missing skill or version does not cause the entire response to fail.
  • The response covers security only. It does not include Skill Card data, generated card status, artifact file lists, or detailed scanner payloads.
  • security.signals contains status-level supporting evidence only; use /scan or the ClawHub security audit page for full scanner details.
  • security.signals.dependencyRegistry is kept for compatibility with v1 responses, but the dependency registry existence scanner has been retired and this key is always null.
  • The absence of a Skill Card does not affect this endpoint's ok, decision, or reasons; clients should read installed skill-card.md locally when they need card content.
  • Use /verify when you need the single-skill Skill Card verification envelope, /card when you need generated card markdown, and /scan when you need detailed scanner data.

Response:

{
  "schema": "clawhub.skill.security-verdicts.v1",
  "items": [
    {
      "ok": true,
      "decision": "pass",
      "reasons": [],
      "requestedSlug": "gifgrep",
      "slug": "gifgrep",
      "displayName": "GifGrep",
      "publisherHandle": "steipete",
      "publisherDisplayName": "Peter",
      "requestedVersion": "1.2.3",
      "version": "1.2.3",
      "createdAt": 0,
      "checkedAt": 0,
      "skillUrl": "https://clawhub.ai/steipete/skills/gifgrep",
      "securityAuditUrl": "https://clawhub.ai/steipete/skills/gifgrep/security-audit?version=1.2.3",
      "security": {
        "status": "clean",
        "passed": true,
        "signals": {
          "staticScan": { "status": "clean", "reasonCodes": [] },
          "virusTotal": null,
          "skillSpector": null,
          "dependencyRegistry": null
        }
      }
    },
    {
      "ok": false,
      "decision": "fail",
      "reasons": ["version.not_found"],
      "requestedSlug": "missing-version",
      "requestedVersion": "1.0.0",
      "error": { "code": "version_not_found", "message": "Version not found" },
      "security": null
    }
  ]
}

GET /api/v1/skills/{slug}/file

Delivers the exact bytes of a stored file as a downloadable response. To request a bounded escaped-text preview, append preview=1; any file whose bytes form valid UTF-8 can be previewed, irrespective of its file extension or MIME metadata.

Query parameters:

  • path (mandatory)
  • version (optional)
  • tag (optional)
  • preview=1 (optional; when the bytes are not valid UTF-8, returns text/plain or 415)

Remarks:

  • The latest version is served by default.
  • Raw downloads are capped at 10MB.
  • Text previews are capped at 200KB.

GET /api/v1/packages

Single catalog endpoint covering:

  • skills
  • code plugins
  • bundle plugins

Query parameters:

  • limit (optional): an integer between 1 and 100
  • cursor (optional): cursor for pagination
  • family (optional): one of skill, code-plugin, or bundle-plugin
  • channel (optional): one of official, community, or private
  • isOfficial (optional): either true or false
  • sort (optional): updated (the default), recommended, trending, downloads, or the legacy alias installs
  • category (optional): a plugin category filter. This filter is supported only when the request is limited to plugin packages (/api/v1/plugins, /api/v1/code-plugins, /api/v1/bundle-plugins, or package endpoints using family=code-plugin/family=bundle-plugin). Controlled categories and legacy v1 filter aliases are listed under GET /api/v1/plugins.

Remarks:

  • Providing invalid values for family, channel, isOfficial, featured, highlightedOnly, or sort results in 400. Unrecognised query parameters are silently ignored.
  • GET /api/v1/code-plugins and GET /api/v1/bundle-plugins remain fixed-family aliases.
  • Skill entries continue to be backed by the skill registry and can still be published exclusively through POST /api/v1/skills.
  • POST /api/v1/packages remains restricted to code-plugin and bundle-plugin releases.
  • Unauthenticated callers see only public package channels.
  • Authenticated callers see private packages for publishers they belong to in list and search results.
  • channel=private returns only those packages the authenticated caller is permitted to read.

GET /api/v1/packages/search

Unified catalog search across skills and plugin packages.

Query parameters:

  • q (required): query string
  • limit (optional): integer (1, 100)
  • family (optional): skill, code-plugin, or bundle-plugin
  • channel (optional): official, community, or private
  • isOfficial (optional): true or false
  • category (optional): plugin category filter. This only applies when the request targets plugin packages. See GET /api/v1/plugins for controlled categories and legacy v1 filter aliases.

Notes:

  • Supplying an invalid value for family, channel, isOfficial, featured, or highlightedOnly results in 400. Unrecognized query parameters are silently dropped.
  • Unauthenticated users can only view public package channels.
  • Authenticated users can search private packages for publishers they are a member of.
  • channel=private restricts results to packages the authenticated caller has read access to.

GET /api/v1/plugins

Browse the plugin catalog, which covers code-plugin and bundle-plugin packages.

Query params:

  • limit (optional): integer (1-100)
  • cursor (optional): pagination cursor
  • isOfficial (optional): true or false
  • sort (optional): recommended (default), trending, downloads, updated, legacy alias installs
  • category (optional): plugin category filter. Current options: channels, models, memory, context, voice, media, web, tools, runtime, gateway, security, other.

Read endpoints still accept legacy v1 filter aliases:

  • mcp-tooling, data, and automation map to tools.
  • observability and deployment map to gateway.
  • dev-tools maps to runtime.

trending shows a seven-day install and download leaderboard, not lifetime totals. On the combined /api/v1/packages endpoint it applies only to plugins; use /api/v1/skills?sort=trending for the skill catalog.

Legacy aliases are not accepted as stored or author-declared category values.

GET /api/v1/skills/export

Bulk export of the most recent public skills for offline analysis.

Authentication:

  • An API token is mandatory.

Query parameters:

  • startDate (required): the lower bound for skill updatedAt, expressed in Unix milliseconds.
  • endDate (required): the upper bound for skill updatedAt, expressed in Unix milliseconds.
  • limit (optional): an integer between 1 and 250, with a default of 250.
  • cursor (optional): a pagination cursor taken from the prior response.

Response:

  • The response body is a ZIP archive.
  • Each exported skill is placed under the directory {publisher}/{slug}/.
  • Skills hosted on the platform include the most recent stored version files and are recorded in _manifest.json alongside sourceRef: "public-clawhub".
  • Skills currently backed by GitHub that have undergone a clean or suspicious scan include _source_handoff.json together with sourceRef: "public-github", the repository, commit, path, content hash, and archive URL. These do not contain source files hosted by ClawHub.
  • Every skill includes _export_skill_meta.json.
  • _manifest.json is always present at the root of the ZIP archive.
  • _errors.json is added when certain skills 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/export

Bulk export of the most recent public plugin releases for offline analysis.

Authentication:

  • An API token is mandatory.

Query parameters:

  • startDate (required): the lower bound for plugin updatedAt, expressed in Unix milliseconds.
  • endDate (required): the upper bound for plugin updatedAt, expressed in Unix milliseconds.
  • limit (optional): an integer between 1 and 250, with a default of 250.
  • cursor (optional): a pagination cursor taken from the prior response.
  • family (optional): either code-plugin or bundle-plugin. When omitted, both plugin families are included.

Response:

  • The response body is a ZIP archive.
  • Each exported plugin is placed under the directory {family}/{packageName}/.
  • For every exported plugin, the stored files from the latest release are included.
  • Per-plugin export metadata is stored at __clawhub_export/{family}/{packageName}/plugin_meta.json.
  • _manifest.json is always present at the root of the ZIP archive.
  • _errors.json is added when certain 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

Search limited to plugins, covering both code-plugin and bundle-plugin packages.

Query parameters:

  • 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 described under GET /api/v1/plugins are also allowed.
  • Category filtering operates as a genuine API filter backed by plugin category digest rows, not a search-query transformation.
  • Results appear in relevance order and currently lack pagination.
  • Browser UI sort controls for plugin search reorder the loaded relevance results, matching the existing /skills browse behavior.

GET /api/v1/packages/{name}

Provides package detail metadata.

Notes:

  • Skills can also be resolved through this route in the unified catalog.
  • Private packages return 404 unless the caller can read the owning publisher.

DELETE /api/v1/packages/{name}

Performs a soft-delete on a package and all its releases.

Notes:

  • Requires an API token for the package owner, an org publisher owner/admin, platform moderator, or platform admin.

GET /api/v1/packages/{name}/versions

Returns version history.

Query params:

  • limit (optional): integer (1, 100)
  • cursor (optional): pagination cursor

Notes:

  • Private packages return 404 unless the caller can read the owning publisher.

GET /api/v1/packages/{name}/versions/{version}

Returns a single package version, including file metadata, compatibility, verification, artifact metadata, and scan data.

Notes:

  • version.artifact.kind is legacy-zip for old-world package archives or npm-pack for ClawPack-backed releases.
  • ClawPack releases include npm-compatible npmIntegrity, npmShasum, and npmTarballName fields.
  • version.sha256hash holds deprecated compatibility metadata intended for older clients. It hashes the exact ZIP bytes returned by /api/v1/packages/{name}/download. Modern clients should use version.artifact.sha256, which identifies the canonical release artifact.
  • version.vtAnalysis, version.llmAnalysis, and version.staticScan are included when scan data exists.
  • Private packages return 404 unless the caller can read the owning publisher.

GET /api/v1/packages/{name}/versions/{version}/security

Returns the exact package release security and trust summary for install clients. This is the public OpenClaw consumption surface for deciding whether a resolved release can be installed.

Auth:

  • Public read endpoint. No owner, publisher, moderator, or admin token is required.

Response:

{
  "package": {
    "name": "@openclaw/example-plugin",
    "displayName": "Example Plugin",
    "family": "code-plugin"
  },
  "release": {
    "releaseId": "packageReleases:...",
    "version": "1.2.3",
    "artifactKind": "npm-pack",
    "artifactSha256": "0123456789abcdef...",
    "npmIntegrity": "sha512-...",
    "npmShasum": "0123456789abcdef0123456789abcdef01234567",
    "npmTarballName": "example-plugin-1.2.3.tgz",
    "createdAt": 1730000000000
  },
  "trust": {
    "scanStatus": "malicious",
    "moderationState": "quarantined",
    "blockedFromDownload": true,
    "reasons": ["manual:quarantined", "scan:malicious"],
    "pending": false,
    "stale": false
  }
}

Response fields:

  • package.name, package.displayName, and package.family point to the resolved registry package.
  • The exact release that underwent evaluation is indicated 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 included.
  • trust.scanStatus represents the final trust status, derived from scanner data and any manual release moderation applied.
  • trust.moderationState can be null. It takes the value null when no manual moderation has been performed on the release.
  • trust.blockedFromDownload signals whether installation should be blocked. Install clients such as OpenClaw ought to halt installation when this field equals true, rather than recalculating block rules from scanner or moderation fields.
  • trust.reasons provides a list of explanations intended for users and audit logs. Reason codes are fixed, concise strings like manual:quarantined, scan:malicious, and package:malicious.
  • trust.pending indicates that at least one trust input has not yet finished processing.
  • trust.stale signals that the trust summary was generated from stale inputs and should be refreshed before a high-confidence allow decision is made.

Notes:

  • This endpoint is tied to a specific version. Clients should invoke it after determining the exact package version they plan to install, not simply after fetching the most recent package metadata.
  • For private packages, 404 is returned unless the caller has access to the owning publisher.
  • This endpoint has a deliberately narrower scope compared to owner or moderator moderation endpoints. It provides the installation decision and a public explanation, but does not expose reporter identities, report content, private evidence, or internal review timelines.

GET /api/v1/packages/{name}/versions/{version}/artifact

Returns the metadata for a package version resolved through the explicit artifact resolver.

Notes:

  • Older package versions produce a legacy-zip artifact along with a legacy ZIP downloadUrl.
  • ClawPack versions yield an npm-pack artifact, npm integrity fields, a tarballUrl, and the compatibility URL for the legacy ZIP.
  • This is the OpenClaw resolver interface; it avoids guessing the archive format from a shared URL.

GET /api/v1/packages/{name}/versions/{version}/artifact/download

Downloads the version artifact via the explicit resolver path.

Notes:

  • ClawPack versions stream the exact uploaded npm-pack .tgz bytes.
  • Legacy ZIP versions redirect to /api/v1/packages/{name}/download?version=.
  • The download rate bucket governs this operation.

GET /api/v1/packages/{name}/readiness

Returns a computed readiness assessment for future OpenClaw consumption.

Readiness checks include:

  • status of the official channel
  • whether the latest version is available
  • availability of the ClawPack npm-pack artifact
  • artifact digest
  • source repository and commit provenance
  • OpenClaw compatibility metadata
  • host targets
  • scan state

Response:

{
  "package": {
    "name": "@openclaw/example-plugin",
    "displayName": "Example Plugin",
    "family": "code-plugin",
    "isOfficial": true,
    "latestVersion": "1.2.3"
  },
  "ready": false,
  "checks": [
    {
      "id": "clawpack",
      "label": "ClawPack artifact",
      "status": "fail",
      "message": "Latest version is legacy ZIP-only."
    }
  ],
  "blockers": ["clawpack"]
}

GET /api/v1/packages/migrations

Moderator endpoint for listing official OpenClaw plugin migration entries.

Auth:

  • Requires an API token belonging to a moderator or admin user.

Query params:

  • phase (optional): planned, published, clawpack-ready, legacy-zip-only, metadata-ready, blocked, ready-for-openclaw, or all (default).
  • limit (optional): integer between 1 and 100
  • cursor (optional): pagination cursor

Response:

{
  "items": [
    {
      "migrationId": "officialPluginMigrations:...",
      "bundledPluginId": "core.search",
      "packageName": "@openclaw/search-plugin",
      "packageId": "packages:...",
      "owner": "platform",
      "sourceRepo": "openclaw/openclaw",
      "sourcePath": "plugins/search",
      "sourceCommit": "abc123",
      "phase": "blocked",
      "blockers": ["missing ClawPack"],
      "hostTargetsComplete": true,
      "scanClean": false,
      "moderationApproved": false,
      "runtimeBundlesReady": false,
      "notes": null,
      "createdAt": 1760000000000,
      "updatedAt": 1760000000000
    }
  ],
  "nextCursor": null,
  "done": true
}

POST /api/v1/packages/migrations

Admin endpoint for creating or updating an official plugin migration entry.

Auth:

  • Requires an API token belonging to an admin user.

Request body:

{
  "bundledPluginId": "core.search",
  "packageName": "@openclaw/search-plugin",
  "owner": "platform",
  "sourceRepo": "openclaw/openclaw",
  "sourcePath": "plugins/search",
  "sourceCommit": "abc123",
  "phase": "blocked",
  "blockers": ["missing ClawPack"],
  "hostTargetsComplete": true,
  "scanClean": false,
  "moderationApproved": false,
  "runtimeBundlesReady": false,
  "notes": "waiting on publisher upload"
}

Notes:

  • bundledPluginId is normalized to lowercase and serves as the stable upsert key.
  • packageName is normalized according to npm naming conventions; the package may be absent for planned migrations.
  • This endpoint only tracks migration readiness. It does not modify OpenClaw or generate ClawPacks.

GET /api/v1/packages/moderation/queue

Moderator or admin endpoint for package release review queues.

Auth:

  • Requires an API token belonging to a moderator or admin user.

Query params:

  • status (optional): open (default), blocked, manual, or all
  • limit (optional): integer between 1 and 100
  • cursor (optional): pagination cursor

Status meanings:

  • open: releases marked as suspicious, malicious, pending, quarantined, revoked, or reported.
  • blocked: releases that are quarantined, revoked, or malicious.
  • manual: any release where a manual moderation override has been applied.
  • all: any release with a manual override, a non-clean scan state, or a package report.
{
  "items": [
    {
      "packageId": "packages:...",
      "releaseId": "packageReleases:...",
      "name": "@openclaw/example-plugin",
      "displayName": "Example Plugin",
      "family": "code-plugin",
      "channel": "community",
      "isOfficial": false,
      "version": "1.2.3",
      "createdAt": 1730000000000,
      "artifactKind": "npm-pack",
      "scanStatus": "malicious",
      "moderationState": "quarantined",
      "moderationReason": "manual review",
      "sourceRepo": "openclaw/example-plugin",
      "sourceCommit": "abc123",
      "reportCount": 2,
      "lastReportedAt": 1730000001000,
      "reasons": ["manual:quarantined", "scan:malicious", "reports:2"]
    }
  ],
  "nextCursor": null,
  "done": true
}

POST /api/v1/packages/{name}/report

Submit a package for moderator review. These reports apply at the package level and may optionally reference a specific version. They enter the moderation queue but do not automatically hide or block downloads; moderators should use release moderation to approve, quarantine, or revoke artifacts.

Auth:

  • An API token is required.

Request:

{ "reason": "Suspicious native binary", "version": "1.2.3" }

Response:

{
  "ok": true,
  "reported": true,
  "alreadyReported": false,
  "packageId": "packages:...",
  "releaseId": "packageReleases:...",
  "reportCount": 1
}

GET /api/v1/packages/reports

Moderator or admin endpoint for accepting package reports.

Auth:

  • Requires an API token belonging to a moderator or admin user.

Query params:

  • status (optional): open (default), confirmed, dismissed, or all
  • limit (optional): integer from 1 to 100
  • cursor (optional): cursor for pagination

Response:

{
  "items": [
    {
      "reportId": "packageReports:...",
      "packageId": "packages:...",
      "releaseId": "packageReleases:...",
      "name": "@openclaw/example-plugin",
      "displayName": "Example Plugin",
      "family": "code-plugin",
      "version": "1.2.3",
      "reason": "Suspicious native binary",
      "status": "open",
      "createdAt": 1730000000000,
      "reporter": {
        "userId": "users:...",
        "handle": "reporter",
        "displayName": "Reporter"
      },
      "triagedAt": null,
      "triagedBy": null,
      "triageNote": null
    }
  ],
  "nextCursor": null,
  "done": true
}

GET /api/v1/packages/{name}/moderation

Endpoint for owners and moderators to view package moderation status.

Auth:

  • Requires an API token for the package owner, a publisher member, a moderator, or an admin user.

Response:

{
  "package": {
    "packageId": "packages:...",
    "name": "@openclaw/example-plugin",
    "displayName": "Example Plugin",
    "family": "code-plugin",
    "channel": "community",
    "isOfficial": false,
    "reportCount": 2,
    "lastReportedAt": 1730000001000,
    "scanStatus": "malicious"
  },
  "latestRelease": {
    "releaseId": "packageReleases:...",
    "version": "1.2.3",
    "artifactKind": "npm-pack",
    "scanStatus": "malicious",
    "moderationState": "quarantined",
    "moderationReason": "manual review",
    "blockedFromDownload": true,
    "reasons": ["manual:quarantined", "scan:malicious", "reports:2"],
    "createdAt": 1730000000000
  }
}

POST /api/v1/packages/reports/{reportId}/triage

Moderator or admin endpoint to resolve or reopen package reports.

Request:

{
  "status": "confirmed",
  "note": "Reviewed and quarantined affected release.",
  "finalAction": "quarantine"
}

note is mandatory when using confirmed or dismissed; it can be left out when reverting status to open. To apply release moderation in the same auditable workflow, pass finalAction: "quarantine" or finalAction: "revoke" with a confirmed report.

Response:

{
  "ok": true,
  "reportId": "packageReports:...",
  "packageId": "packages:...",
  "status": "confirmed",
  "reportCount": 0
}

POST /api/v1/packages/{name}/versions/{version}/moderation

Moderator or admin endpoint for reviewing a package release.

Request:

{ "state": "quarantined", "reason": "Suspicious native payload." }

Supported states:

  • approved: manually reviewed and approved.
  • quarantined: blocked while awaiting follow-up.
  • revoked: blocked after a previously trusted release.

Quarantined and revoked releases return 403 from artifact download routes. Every change is recorded in the audit log.

GET /api/v1/packages/{name}/file

Returns the exact stored package file bytes as a download. Append preview=1 to request the same bounded UTF-8 text preview used for skill files.

Query params:

  • path (required)
  • version (optional)
  • tag (optional)
  • preview=1 (optional; returns text/plain or 415 when the bytes are not valid UTF-8)

Notes:

  • Defaults to the most recent release.
  • Uses the read rate bucket, not the download bucket.
  • Raw download limit: 10 MB.
  • Text preview limit: 200 KB; opaque files return 415 only for preview requests.
  • Pending VirusTotal scans do not block reads; 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 params:

  • version (optional)
  • tag (optional)

Notes:

  • Defaults to the most recent release.
  • Skills redirect to GET /api/v1/download.
  • Plugin and package archives use a package/ root directory in the zip file so older OpenClaw clients continue to function.
  • This route serves only ZIP files. It does not stream ClawPack .tgz archives.
  • 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.
  • Pending VirusTotal scans do not block downloads; malicious releases return 403.
  • Private packages return 404 unless the caller is the owner.

GET /api/npm/{package}

Returns an npm-compatible packument for package versions backed by ClawPack.

Notes:

  • Only ClawPack npm-pack tarballs that have been uploaded are shown in the version list.
  • Versions that only exist as legacy ZIP files are deliberately excluded.
  • dist.tarball, dist.integrity, and dist.shasum use fields compatible with npm, so users can configure npm to target the mirror if desired.
  • 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

Sends the exact uploaded ClawPack tarball byte stream for npm mirror clients.

Notes:

  • The download rate bucket applies here.
  • Response headers include ClawHub SHA-256 along with npm integrity and shasum metadata.
  • Moderation and private package access checks remain enforced.

GET /api/v1/resolve

The CLI uses this endpoint to associate a local fingerprint with a known version.

Query parameters:

  • slug (required)
  • hash (required): a 64-character hex sha256 of the bundle fingerprint

Response:

{ "slug": "gifgrep", "match": { "version": "1.2.2" }, "latestVersion": { "version": "1.2.3" } }

GET /api/v1/download

Retrieves a hosted skill version ZIP, or provides a GitHub source handoff for a current GitHub-backed skill that has a clean or suspicious scan and no hosted version.

Query parameters:

  • slug (required)
  • version (optional): a semver string
  • tag (optional): a tag name, for example latest

Notes:

  • When neither version nor tag is supplied, the most recent version is selected.
  • Soft-deleted versions return 410.
  • Handoffs for GitHub-backed skills do not proxy or mirror bytes. The JSON response contains sourceRef: "public-github", repo, commit, path, contentHash, and archiveUrl; the scan or current state acts as a gate and is not included as success payload metadata.
  • Download statistics are counted as unique identities per UTC day (userId when an API token is valid, otherwise by IP).

Auth endpoints (Bearer token)

Every endpoint requires:

Authorization: Bearer clh_...

GET /api/v1/whoami

Validates the token and returns the user handle.

POST /api/v1/skills

Creates and publishes a new version.

  • Recommended approach: multipart/form-data with payload JSON and files[] blobs.
  • A JSON body that includes files (storageId-based) is also acceptable.
  • Optional payload field: ownerHandle. If present, the API resolves that publisher server-side and the actor must have publisher access.
  • Optional payload field: migrateOwner. When true with ownerHandle, an existing skill can be transferred to that owner provided the actor is an admin or owner on both the current and target publishers. Without this opt-in, owner changes are denied.

POST /api/v1/packages

Publishes a code-plugin or bundle-plugin release.

  • Bearer token authentication is required.
  • multipart/form-data is mandatory.
  • Allowed form fields include payload, repeated files blobs, or a single clawpack tarball reference. clawpack can be a .tgz blob or a storage id obtained from the upload-url flow. Staged storage-id publishes must also include the clawpackUploadTicket that came with that upload URL.
  • Use either files or clawpack, but never both in one request.
  • JSON bodies and caller-supplied payload.files or payload.artifact metadata are not accepted.
  • Direct multipart publishes are limited to 18MB. ClawPack tarballs can use the upload-url flow up to the 120MB tarball limit.
  • Optional payload field: ownerHandle. When present, only admins may publish on behalf of that owner.

Validation highlights:

  • The value of family must be either code-plugin or bundle-plugin.
  • Plugin packages depend on openclaw.plugin.json. When uploading a ClawPack .tgz, that file must be placed 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 not required metadata.
  • Only the openclaw org publisher and personal publishers of current openclaw org members are allowed to publish to the official channel.
  • Even when publishing on behalf of someone else, official-channel eligibility is still validated against the target owner's account.

DELETE /api/v1/skills/{slug} / POST /api/v1/skills/{slug}/undelete

Soft-delete or restore a skill (available to owners, moderators, or admins).

Optional JSON body:

{ "reason": "Held for moderation pending legal review." }

If provided, reason gets saved as the skill moderation note and is also recorded in the audit log. When an owner performs a soft delete, the slug stays reserved for 30 days; after that period, another publisher can claim it. The delete response includes slugReservedUntil when this reservation applies. Moderator or admin soft deletes, as well as security removals, do not have an expiration on the slug.

Delete response:

{ "ok": true, "slugReservedUntil": 1730000000000 }

Status codes:

  • 200: ok
  • 401: unauthorized
  • 403: forbidden
  • 404: skill or user not found
  • 500: internal server error

POST /api/v1/users/publisher

Restricted to admins. This endpoint guarantees an org publisher exists for a given handle. If the handle still references a legacy shared user or personal publisher, it is first migrated into an org publisher. For a new org, supply memberHandle; the acting admin is not added as a member. memberRole defaults to owner.

  • Body: { "handle": "openclaw", "displayName": "OpenClaw", "memberHandle": "alice", "memberRole": "owner", "trusted": true }
  • Response: { "ok": true, "publisherId": "...", "handle": "openclaw", "created": true, "migrated": false, "trusted": true, "member": { "userId": "...", "handle": "alice", "role": "owner" } }

POST /api/v1/publishers

Authenticated self-service creation of an org publisher. This creates a new org publisher and makes the caller an owner. It does not migrate existing user or personal handles, nor does it mark the publisher as trusted or official.

  • Body: { "handle": "opik", "displayName": "Opik" }
  • Response: { "ok": true, "publisherId": "...", "handle": "opik", "created": true, "trusted": false }
  • Returns 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 the rightful owner without requiring a release to be published. Package names become private placeholder packages with no release rows, so the same owner can later publish the actual code-plugin or bundle-plugin release into that name.

  • Body: { "handle": "openclaw", "slugs": ["diffs"], "packageNames": ["@openclaw/diffs"], "reason": "reserved for official OpenClaw plugin" }
  • Response: { "ok": true, "succeeded": 2, "failed": 0, "results": [{ "kind": "slug", "name": "diffs", "ok": true, "action": "reserved" }] }

POST /api/v1/users/publisher-recovery

Admin-only. Recovers a personal publisher for a verified replacement GitHub OAuth principal without modifying Convex Auth account rows. The request must specify both immutable GitHub provider account IDs; mutable handles are used only as an operator-facing safeguard.

The endpoint runs in dry-run mode by default. To apply the recovery, dryRun: false and confirmIdentityVerified: true are required after staff independently verify continuity between the two GitHub principals. Recovery fails closed if the destination user's current personal publisher has any skills, packages, or GitHub skill sources. Recovery also migrates legacy ownerUserId fields for the recovered publisher's skills, skill slug aliases, packages, package inspector warnings, and derived search digest rows so that direct-owner paths align with the new publisher authority. An active protected-handle reservation for the recovered handle is reassigned to the replacement user, preventing later profile synchronization from restoring the former user's competing authority. Each primary table is limited to 100 rows per apply transaction; larger recoveries must first use a resumable owner migration. GitHub skill sources are publisher-scoped and reported as checked rather than rewritten.

  • Body: { "handle": "gingiris", "nextUserHandle": "gingiris-1031", "previousGitHubProviderAccountId": "123", "nextGitHubProviderAccountId": "456", "reason": "Verified account continuity for issue #2555", "confirmIdentityVerified": true, "dryRun": false }
  • Response: { "ok": true, "dryRun": false, "recovered": true, "publisherId": "...", "handle": "gingiris", "previousUser": { "userId": "...", "handle": "gingiris", "nextHandle": "gingiris-recovered", "githubProviderAccountId": "123", "authAccountCount": 1 }, "nextUser": { "userId": "...", "handle": "gingiris-1031", "nextHandle": "gingiris", "githubProviderAccountId": "456", "authAccountCount": 1 }, "retiredPersonalPublisher": null, "resourceOwnerMigration": { "limitPerTable": 100, "skills": 1, "skillSlugAliases": 1, "packages": 0, "packageInspectorWarnings": 0, "githubSourcesChecked": 1, "handleReservations": 1 }, "identityVerified": true, "reason": "Verified account continuity for issue #2555" }

Owner slug management endpoints

  • POST /api/v1/skills/{slug}/rename
    • Body: { "newSlug": "new-canonical-slug" }
    • Response: { "ok": true, "slug": "new-canonical-slug", "previousSlug": "old-slug" }
  • POST /api/v1/skills/{slug}/merge
    • Body: { "targetSlug": "canonical-target-slug" }
    • Response: { "ok": true, "sourceSlug": "old-slug", "targetSlug": "canonical-target-slug" }

Notes:

  • Both endpoints require API token authentication and only work for the skill owner.
  • rename keeps the previous slug as a redirect alias.
  • merge conceals the source listing and redirects the source slug to the target listing.

Transfer ownership endpoints

  • POST /api/v1/skills/{slug}/transfer
    • Body: { "toUserHandle": "target_handle", "message": "optional" }
    • Response: { "ok": true, "transferId": "skillOwnershipTransfers:...", "toUserHandle": "target_handle", "expiresAt": 1730000000000 }
  • 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

Remove a user and permanently delete their owned skills (requires moderator or admin privileges).

Body:

{ "handle": "user_handle", "reason": "optional ban reason" }

or

{ "userId": "users_...", "reason": "optional ban reason" }

Response:

{ "ok": true, "alreadyBanned": false, "deletedSkills": 3 }

POST /api/v1/users/unban

Reverse a ban and reinstate eligible skills (admin only).

Body:

{ "handle": "user_handle", "reason": "optional unban reason" }

or

{ "userId": "users_...", "reason": "optional unban reason" }

Response:

{ "ok": true, "alreadyUnbanned": false, "restoredSkills": 3 }

POST /api/v1/users/reclassify-ban

Update the reason associated with an active ban without lifting the ban or restoring content (admin only). By default this operates as a dry run unless dryRun is set to false.

Body:

{ "handle": "user_handle", "reason": "bulk publishing spam", "dryRun": true }

or

{ "userId": "users_...", "reason": "bulk publishing spam", "dryRun": false }

Response:

{
  "ok": true,
  "dryRun": false,
  "userId": "users_...",
  "handle": "user_handle",
  "previousReason": "malware auto-ban",
  "nextReason": "bulk publishing spam",
  "changed": true
}

POST /api/v1/users/role

Modify a user's role (admin only).

Body:

{ "handle": "user_handle", "role": "moderator" }

or

{ "userId": "users_...", "role": "admin" }

Response:

{ "ok": true, "role": "moderator" }

GET /api/v1/users

Retrieve or search users (admin only).

Query parameters:

  • q (optional): search term
  • query (optional): alternative name for q
  • limit (optional): maximum number of results (default 20, cap 200)

Response:

{
  "items": [
    {
      "userId": "users_...",
      "handle": "user_handle",
      "displayName": "User",
      "name": "User",
      "role": "moderator"
    }
  ],
  "total": 1
}

POST /api/v1/stars/{slug} / DELETE /api/v1/stars/{slug}

Create or delete a Bookmark. The older stars endpoint and its response field names are kept for backward compatibility. Both operations are idempotent.

Responses:

{ "ok": true, "starred": true, "alreadyStarred": false }
{ "ok": true, "unstarred": true, "alreadyUnstarred": false }

Legacy CLI endpoints (deprecated)

Kept for compatibility with older CLI versions:

  • GET /api/cli/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

Check DEPRECATIONS.md for when these will be removed.

POST /api/cli/upload-url produces uploadUrl and uploadTicket. When a package publish stages a ClawPack tarball, the resulting storage identifier must be passed as clawpack and the returned ticket as clawpackUploadTicket.

Registry discovery (/.well-known/clawhub.json)

The CLI can retrieve registry and authentication settings from the site:

  • /.well-known/clawhub.json (JSON, recommended)
  • /.well-known/clawdhub.json (older format)

Schema:

{ "apiBase": "https://clawhub.ai", "authBase": "https://clawhub.ai", "minCliVersion": "0.0.5" }

If you run a self-hosted instance, serve this file (or set CLAWHUB_REGISTRY explicitly; legacy CLAWDHUB_REGISTRY).