| summary | read_when | ||
|---|---|---|---|
HTTP API reference (public + CLI endpoints + auth). |
|
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: 120/min per IP, 600/min per key
-
Write: 30/min per IP, 120/min per key
-
Download: 20/min per IP, 120/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 skills
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 (only forsort=updated)sort(optional):updated(default),downloads,stars(alias:rating),installsCurrent(alias:installs),installsAllTime,trending
Notes:
trendingranks by installs in the last 7 days (telemetry-based).
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:
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.
Returns raw text content.
Query params:
path(required)version(optional)tag(optional)
Notes:
- Defaults to latest version.
- File size limit: 200KB.
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.
Soft-delete / restore a skill (owner, moderator, or admin).
Status codes:
200: ok401: unauthorized403: forbidden404: skill/user not found500: internal server error
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).