| summary | HTTP API reference (public + CLI endpoints + auth). | ||
|---|---|---|---|
| read_when |
|
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.
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: 180/min per IP, 900/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 secondsRateLimit-Reset: seconds until reset (delay)Retry-After: seconds to wait before retry (delay) on429
Example 429 response:
HTTP/2 429
content-type: text/plain; charset=utf-8
x-ratelimit-limit: 20
x-ratelimit-remaining: 0
x-ratelimit-reset: 1771404540
ratelimit-limit: 20
ratelimit-remaining: 0
ratelimit-reset: 34
retry-after: 34
Rate limit exceededClient guidance:
- If
Retry-Afterexists, wait that many seconds before retry. - Use jittered backoff to avoid synchronized retries.
- If
Retry-Afteris missing, fallback toRateLimit-Reset(or compute fromX-RateLimit-Reset).
IP source:
- Uses
cf-connecting-ip(Cloudflare) for client IP by default. - Set
TRUST_FORWARDED_IPS=trueto opt in tox-forwarded-for,x-real-ip, orfly-client-ip(non-Cloudflare deployments). - 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.
Query params:
q(required): query stringlimit(optional): integerhighlightedOnly(optional):trueto filter to highlighted skillsnonSuspiciousOnly(optional):trueto hide suspicious (flagged.suspicious) skillsnonSuspicious(optional): legacy alias fornonSuspiciousOnly
Response:
{
"results": [
{
"score": 0.123,
"slug": "gifgrep",
"displayName": "GifGrep",
"summary": "…",
"version": "1.2.3",
"updatedAt": 1730000000000
}
]
}Notes:
- Results are returned in relevance order (embedding similarity + exact slug/name token boosts + popularity prior from downloads).
Query params:
limit(optional): integer (1–200)cursor(optional): pagination cursor for any non-trendingsortsort(optional):updated(default),downloads,stars(alias:rating),installsCurrent(alias:installs),installsAllTime,trendingnonSuspiciousOnly(optional):trueto hide suspicious (flagged.suspicious) skillsnonSuspicious(optional): legacy alias fornonSuspiciousOnly
Notes:
trendingranks by installs in the last 7 days (telemetry-based).- When
nonSuspiciousOnly=true, cursor-based sorts may return fewer thanlimititems on a page because suspicious skills are filtered after page retrieval. - Use
nextCursorto continue pagination when present. A short page does not by itself mean end-of-results.
Response:
{
"items": [
{
"slug": "gifgrep",
"displayName": "GifGrep",
"summary": "…",
"tags": { "latest": "1.2.3" },
"stats": {},
"createdAt": 0,
"updatedAt": 0,
"latestVersion": { "version": "1.2.3", "createdAt": 0, "changelog": "…" },
"metadata": { "os": ["macos"], "systems": ["aarch64-darwin"] }
}
],
"nextCursor": null
}Response:
{
"skill": {
"slug": "gifgrep",
"displayName": "GifGrep",
"summary": "…",
"tags": { "latest": "1.2.3" },
"stats": {},
"createdAt": 0,
"updatedAt": 0
},
"latestVersion": { "version": "1.2.3", "createdAt": 0, "changelog": "…" },
"metadata": { "os": ["macos"], "systems": ["aarch64-darwin"] },
"owner": { "handle": "steipete", "displayName": "Peter", "image": null },
"moderation": {
"isSuspicious": false,
"isMalwareBlocked": false,
"verdict": "clean",
"reasonCodes": [],
"summary": null,
"engineVersion": "v2.0.0",
"updatedAt": 0
}
}Notes:
- Old slugs created by owner rename/merge flows resolve to the canonical skill.
metadata.os: OS restrictions declared in skill frontmatter (e.g.["macos"],["linux"]).nullif not declared.metadata.systems: Nix system targets (e.g.["aarch64-darwin", "x86_64-linux"]).nullif not declared.metadataisnullif the skill has no platform metadata.moderationis included only when the skill is flagged or the owner is viewing it.
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
200for already-flagged visible skills. - Evidence is redacted for public callers and only includes raw snippets for owners/staff.
Query params:
limit(optional): integercursor(optional): pagination cursor
Returns version metadata + files list.
version.securityincludes normalized scan verification status and scanner details (VirusTotal + LLM), when available.
Returns security scan verification details for a skill version.
Query params:
version(optional): specific version string.tag(optional): resolve a tagged version (for examplelatest).
Notes:
- If neither
versionnortagis provided, uses the latest version. - Includes normalized verification status plus scanner-specific details.
security.capabilityTagsincludes deterministic capability/risk labels such ascrypto,requires-wallet,can-make-purchases,can-sign-transactions,requires-oauth-token, andposts-externallywhen detected.security.hasScanResultistrueonly when a scanner produced a definitive verdict (clean,suspicious, ormalicious).moderationis a current skill-level moderation snapshot derived from the latest version.- When querying a historical version, check
moderation.matchesRequestedVersionandmoderation.sourceVersionbefore treatingmoderationandsecurityas the same version context.
Returns raw text content.
Query params:
path(required)version(optional)tag(optional)
Notes:
- Defaults to latest version.
- File size limit: 200KB.
Unified catalog endpoint for:
- skills
- code plugins
- bundle plugins
Query params:
limit(optional): integer (1–100)cursor(optional): pagination cursorfamily(optional):skill,code-plugin, orbundle-pluginchannel(optional):official,community, orprivateisOfficial(optional):trueorfalseexecutesCode(optional):trueorfalsecapabilityTag(optional): capability filter for plugin packages
Notes:
GET /api/v1/code-pluginsandGET /api/v1/bundle-pluginsremain fixed-family aliases.- Skill entries stay backed by the skill registry and can still be published only through
POST /api/v1/skills. POST /api/v1/packagesis still only for code-plugin and bundle-plugin releases.- Anonymous callers only see public package channels.
- Authenticated callers can see private packages for publishers they belong to in list/search results.
channel=privateonly returns packages the authenticated caller can read.
Unified catalog search across skills + plugin packages.
Query params:
q(required): query stringlimit(optional): integer (1–100)family(optional):skill,code-plugin, orbundle-pluginchannel(optional):official,community, orprivateisOfficial(optional):trueorfalseexecutesCode(optional):trueorfalsecapabilityTag(optional): capability filter for plugin packages
Notes:
- Anonymous callers only see public package channels.
- Authenticated callers can search private packages for publishers they belong to.
channel=privateonly returns packages the authenticated caller can read.
Returns package detail metadata.
Notes:
- Skills can also resolve through this route in the unified catalog.
- Private packages return
404unless the caller can read the owning publisher.
Returns version history.
Query params:
limit(optional): integer (1–100)cursor(optional): pagination cursor
Notes:
- Private packages return
404unless the caller can read the owning publisher.
Returns one package version, including file metadata, compatibility, capabilities, verification, and scan data.
Notes:
version.sha256hash,version.vtAnalysis,version.llmAnalysis, andversion.staticScanare included when scan data exists.- Private packages return
404unless the caller can read the owning publisher.
Returns raw text content for a package file.
Query params:
path(required)version(optional)tag(optional)
Notes:
- Defaults to the latest release.
- Uses the read rate bucket, not the download bucket.
- Binary files return
415. - File size limit: 200KB.
- Pending VirusTotal scans do not block reads; malicious releases may still be withheld elsewhere.
- Private packages return
404unless the caller can read the owning publisher.
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
404unless the caller is the owner.
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" } }Downloads a zip of a skill version.
Query params:
slug(required)version(optional): semver stringtag(optional): tag name (e.g.latest)
Notes:
- If neither
versionnortagis provided, the latest version is used. - Soft-deleted versions return
410. - Download stats are counted as unique identities per hour (
userIdwhen API token is valid, otherwise IP).
All endpoints require:
Authorization: Bearer clh_...
Validates token and returns the user handle.
Publishes a new version.
- Preferred:
multipart/form-datawithpayloadJSON +files[]blobs. - JSON body with
files(storageId-based) is also accepted.
Publishes a code-plugin or bundle-plugin release.
- Requires Bearer token auth.
- Preferred:
multipart/form-datawithpayloadJSON +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:
familymust becode-pluginorbundle-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
officialchannel. - On-behalf publishes still validate official-channel eligibility against the target owner account.
Soft-delete / restore a skill (owner, moderator, or admin).
Status codes:
200: ok401: unauthorized403: forbidden404: skill/user not found500: internal server error
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 }
POST /api/v1/skills/{slug}/rename- Body:
{ "newSlug": "new-canonical-slug" } - Response:
{ "ok": true, "slug": "new-canonical-slug", "previousSlug": "old-slug" }
- Body:
POST /api/v1/skills/{slug}/merge- Body:
{ "targetSlug": "canonical-target-slug" } - Response:
{ "ok": true, "sourceSlug": "old-slug", "targetSlug": "canonical-target-slug" }
- Body:
Notes:
- Both endpoints require API token auth and only work for the skill owner.
renamepreserves the previous slug as a redirect alias.mergehides the source listing and redirects the source slug to the target listing.
POST /api/v1/skills/{slug}/transfer- Body:
{ "toUserHandle": "target_handle", "message": "optional" } - Response:
{ "ok": true, "transferId": "skillOwnershipTransfers:...", "toUserHandle": "target_handle", "expiresAt": 1730000000000 }
- Body:
POST /api/v1/skills/{slug}/transfer/acceptPOST /api/v1/skills/{slug}/transfer/rejectPOST /api/v1/skills/{slug}/transfer/cancel- Response (accept/reject/cancel):
{ "ok": true, "skillSlug": "demo-skill?" }
- Response (accept/reject/cancel):
GET /api/v1/transfers/incomingGET /api/v1/transfers/outgoing- Response shape:
{ "transfers": [{ "_id": "...", "skill": { "slug": "demo", "displayName": "Demo" }, "fromUser"|"toUser": { "handle": "..." }, "message": "...", "requestedAt": 0, "expiresAt": 0 }] }
- Response shape:
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 }Change a user role (admin only).
Body:
{ "handle": "user_handle", "role": "moderator" }or
{ "userId": "users_...", "role": "admin" }Response:
{ "ok": true, "role": "moderator" }List or search users (admin only).
Query params:
q(optional): search queryquery(optional): alias forqlimit(optional): max results (default 20, max 200)
Response:
{
"items": [
{
"userId": "users_...",
"handle": "user_handle",
"displayName": "User",
"name": "User",
"role": "moderator"
}
],
"total": 1
}Add/remove a star (highlights). Both endpoints are idempotent.
Responses:
{ "ok": true, "starred": true, "alreadyStarred": false }{ "ok": true, "unstarred": true, "alreadyUnstarred": false }Still supported for older CLI versions:
GET /api/cli/whoamiPOST /api/cli/upload-urlPOST /api/cli/publishPOST /api/cli/telemetry/syncPOST /api/cli/skill/deletePOST /api/cli/skill/undelete
See DEPRECATIONS.md for removal plan.
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).