Files
clawhub/docs/http-api.md
T

20 KiB
Raw Blame History

summary, read_when
summary read_when
HTTP API reference (public + CLI endpoints + auth).
Adding/changing endpoints
Debugging CLI ↔ registry requests

HTTP API

Base URL: https://clawhub.ai (default).

All v1 paths are under /api/v1/... and implemented by Convex HTTP routes (convex/http.ts). Legacy /api/... and /api/cli/... remain for compatibility (see DEPRECATIONS.md). OpenAPI: /api/v1/openapi.json.

Public catalog reuse

Third-party directories may use the public read endpoints to list or search ClawHub skills. Please cache results, honor 429/Retry-After, link users back to the canonical ClawHub listing (https://clawhub.ai/<owner>/<slug>), and avoid implying ClawHub endorsement of the third-party site. Do not attempt to mirror hidden, private, or moderation-blocked content outside the public API surface.

Rate limits

Enforcement model:

  • Anonymous requests: enforced per IP.

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

  • If token is missing/invalid, behavior falls back to IP enforcement.

  • Read: 600/min per IP, 2400/min per key

  • Write: 45/min per IP, 180/min per key

  • Download: 30/min per IP, 180/min per key (/api/v1/download)

Headers:

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

Header semantics:

  • X-RateLimit-Reset: absolute Unix epoch seconds
  • RateLimit-Reset: seconds until reset (delay)
  • 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 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:

  • Uses cf-connecting-ip (Cloudflare) for client IP by default.
  • Set TRUST_FORWARDED_IPS=true to opt in to x-forwarded-for, x-real-ip, or fly-client-ip (non-Cloudflare deployments).
  • If no trusted client IP is available, anonymous download requests use an endpoint-scoped fallback bucket instead of one global ip:unknown bucket. Anonymous read/write requests still use the shared unknown bucket so missing-IP deployments remain visible and conservative.
  • If you run behind a reverse proxy/load balancer, ensure real client IP headers are preserved and trusted correctly, or rate limits may be too strict due to shared proxy IPs.

Public endpoints (no auth)

GET /api/v1/search

Query params:

  • q (required): query string
  • limit (optional): integer
  • highlightedOnly (optional): true to filter to highlighted skills
  • nonSuspiciousOnly (optional): true to hide 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
    }
  ]
}

Notes:

  • Results are returned in relevance order (embedding similarity + exact slug/name token boosts + popularity prior from downloads).
  • Relevance is stronger than popularity. A precise slug or display-name token match can outrank a looser match with many more downloads.
  • ASCII text is tokenized on word and punctuation boundaries. For example, personal-map contains a standalone map token, while amap-jsapi-skill contains amap, jsapi, and skill; searching for map therefore gives personal-map a stronger lexical match than amap-jsapi-skill.
  • Downloads are used as a small log-scaled prior and tie-breaker, not as the primary ranking signal. High-download skills can rank lower when the query text is a weaker match.
  • Suspicious or hidden moderation state can remove a skill from public search depending on caller filters and current moderation status.

Publisher discoverability guidance:

  • Put the terms users will literally search for in the display name, summary, and tags. Use a standalone slug token only when it is also a stable identity you want to keep.
  • Do not rename a slug just to chase one query unless the new slug is a better long-term canonical name. Old slugs become redirect aliases, but the canonical URL, displayed slug, and future search digests use the new slug.
  • Rename aliases preserve resolution for old URLs and installs that resolve through the registry, but search ranking is based on the canonical skill metadata after the rename has indexed. Existing stats stay with the skill.
  • If a skill is unexpectedly invisible, check moderation state first with clawhub inspect <slug> while logged in before changing ranking-related metadata.

GET /api/v1/skills

Query params:

  • limit (optional): integer (1200)
  • cursor (optional): pagination cursor for any non-trending sort
  • sort (optional): updated (default), createdAt (alias: newest), downloads, stars (alias: rating), installsCurrent (alias: installs), installsAllTime, trending
  • nonSuspiciousOnly (optional): true to hide suspicious (flagged.suspicious) skills
  • nonSuspicious (optional): legacy alias for nonSuspiciousOnly

Notes:

  • trending ranks by installs in the last 7 days (telemetry-based).
  • createdAt is stable for new-skill crawls; updated changes when existing skills are republished.
  • When nonSuspiciousOnly=true, cursor-based sorts may return fewer than limit items on a page because suspicious skills are filtered after page retrieval.
  • Use nextCursor to continue pagination when present. A short page does not by itself mean end-of-results.

Response:

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

GET /api/v1/skills/{slug}

Response:

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

Notes:

  • Old slugs created by owner rename/merge flows resolve to the canonical skill.
  • metadata.os: OS restrictions declared in skill frontmatter (e.g. ["macos"], ["linux"]). 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 staff can access moderation details for hidden skills.
  • Public callers only get 200 for already-flagged visible skills.
  • Evidence is redacted for public callers and only includes raw snippets for owners/staff.

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 + files list.

  • version.security includes normalized scan verification status and scanner details (VirusTotal + LLM), when available.

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

Returns security scan verification details for a skill version.

Query params:

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

Notes:

  • If neither version nor tag is provided, uses the latest version.
  • Includes normalized verification status plus scanner-specific details.
  • security.capabilityTags includes deterministic capability/risk labels such as crypto, requires-wallet, can-make-purchases, can-sign-transactions, requires-oauth-token, and posts-externally when detected.
  • security.hasScanResult is true only when a scanner produced a definitive verdict (clean, suspicious, or malicious).
  • moderation is a current skill-level moderation snapshot derived from the latest version.
  • When querying a historical version, check moderation.matchesRequestedVersion and moderation.sourceVersion before treating moderation and security as the same version context.

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

Returns raw text content.

Query params:

  • path (required)
  • version (optional)
  • tag (optional)

Notes:

  • Defaults to latest version.
  • File size limit: 200KB.

GET /api/v1/packages

Unified catalog endpoint for:

  • skills
  • code plugins
  • bundle plugins

Query params:

  • limit (optional): integer (1100)
  • cursor (optional): pagination cursor
  • family (optional): skill, code-plugin, or bundle-plugin
  • channel (optional): official, community, or private
  • isOfficial (optional): true or false
  • executesCode (optional): true or false
  • capabilityTag (optional): capability filter for plugin packages

Notes:

  • GET /api/v1/code-plugins and GET /api/v1/bundle-plugins remain fixed-family aliases.
  • Skill entries stay backed by the skill registry and can still be published only through POST /api/v1/skills.
  • POST /api/v1/packages is still only for code-plugin and bundle-plugin releases.
  • Anonymous callers only see public package channels.
  • Authenticated callers can see private packages for publishers they belong to in list/search results.
  • channel=private only returns packages the authenticated caller can read.

GET /api/v1/packages/search

Unified catalog search across skills + plugin packages.

Query params:

  • q (required): query string
  • limit (optional): integer (1100)
  • family (optional): skill, code-plugin, or bundle-plugin
  • channel (optional): official, community, or private
  • isOfficial (optional): true or false
  • executesCode (optional): true or false
  • capabilityTag (optional): capability filter for plugin packages

Notes:

  • Anonymous callers only see public package channels.
  • Authenticated callers can search private packages for publishers they belong to.
  • channel=private only returns packages the authenticated caller can read.

GET /api/v1/packages/{name}

Returns package detail metadata.

Notes:

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

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

Returns version history.

Query params:

  • limit (optional): integer (1100)
  • 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 one package version, including file metadata, compatibility, capabilities, verification, and scan data.

Notes:

  • version.sha256hash, 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}/file

Returns raw text content for a package file.

Query params:

  • path (required)
  • version (optional)
  • tag (optional)

Notes:

  • Defaults to the latest release.
  • Uses the read rate bucket, not the download bucket.
  • Binary files return 415.
  • File size limit: 200KB.
  • Pending VirusTotal scans do not block reads; malicious releases may still be withheld elsewhere.
  • Private packages return 404 unless the caller can read the owning publisher.

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

Downloads a deterministic package archive for a package release.

Query params:

  • version (optional)
  • tag (optional)

Notes:

  • Defaults to the latest release.
  • Skills redirect to GET /api/v1/download.
  • Plugin/package archives are zip files with a package/ root so they install directly in OpenClaw without repacking.
  • 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/v1/resolve

Used by the CLI to map a local fingerprint to a known version.

Query params:

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

Response:

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

GET /api/v1/download

Downloads a zip of a skill version.

Query params:

  • slug (required)
  • version (optional): semver string
  • tag (optional): tag name (e.g. latest)

Notes:

  • If neither version nor tag is provided, the latest version is used.
  • Soft-deleted versions return 410.
  • Download stats are counted as unique identities per hour (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.

  • Preferred: multipart/form-data with payload JSON + files[] blobs.
  • JSON body with files (storageId-based) is also accepted.

POST /api/v1/packages

Publishes a code-plugin or bundle-plugin release.

  • Requires Bearer token auth.
  • Preferred: multipart/form-data with payload JSON + files[] blobs.
  • JSON body with files (storageId-based) is also accepted.
  • Optional payload field: ownerHandle. When present, only admins may publish on behalf of that owner.

Validation highlights:

  • family must be code-plugin or bundle-plugin.
  • Code plugins require package.json, openclaw.plugin.json, source repo metadata, source commit metadata, and config schema metadata.
  • Bundle plugins require at least one host target.
  • Only trusted publishers may publish to the official channel.
  • On-behalf publishes still validate official-channel eligibility against the target owner account.

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

Soft-delete / restore a skill (owner, moderator, or admin).

Optional JSON body:

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

When present, reason is stored as the skill moderation note and copied into the audit log.

Status codes:

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

POST /api/v1/users/publisher

Admin-only. Ensures an org publisher exists for a handle. If the handle still points at a legacy shared user/personal publisher, the endpoint migrates it into an org publisher first.

  • Body: { "handle": "openclaw", "displayName": "OpenClaw", "trusted": true }
  • Response: { "ok": true, "publisherId": "...", "handle": "openclaw", "created": true, "migrated": false, "trusted": true }

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 auth and only work for the skill owner.
  • rename preserves the previous slug as a redirect alias.
  • merge hides 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

Ban a user and hard-delete owned skills (moderator/admin only).

Body:

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

or

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

Response:

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

POST /api/v1/users/unban

Unban a user and restore eligible skills (admin only).

Body:

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

or

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

Response:

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

POST /api/v1/users/role

Change a user role (admin only).

Body:

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

or

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

Response:

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

GET /api/v1/users

List or search users (admin only).

Query params:

  • q (optional): search 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/remove a star (highlights). Both endpoints are idempotent.

Responses:

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

Legacy CLI endpoints (deprecated)

Still supported for older CLI versions:

  • GET /api/cli/whoami
  • POST /api/cli/upload-url
  • POST /api/cli/publish
  • POST /api/cli/telemetry/sync
  • POST /api/cli/skill/delete
  • POST /api/cli/skill/undelete

See DEPRECATIONS.md for removal plan.

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

The CLI can discover registry/auth settings from the site:

  • /.well-known/clawhub.json (JSON, preferred)
  • /.well-known/clawdhub.json (legacy)

Schema:

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

If you self-host, serve this file (or set CLAWHUB_REGISTRY explicitly; legacy CLAWDHUB_REGISTRY).