Products API
Complete reference for product endpoints: search, upload, scan, download, like, reviews, self-approve, and CLI create.
The Products API covers every product lifecycle operation: searching the catalog, uploading files, scanning content, downloading purchased products, social interactions (likes and reviews), and publishing.
Search products
Search the published product catalog. No authentication required.
GET /cli/products/searchQuery parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
q | string | No | "" | Search query (matched against title, description, slug, tags). Max 200 chars. |
category | string | No | all | Filter by category. Valid: skills, squads, agents, workflows, design-systems, claude-md, prompts, applications, systems |
author | string | No | all | Filter by author username (leading @ is stripped) |
free | string | No | "false" | Set to "true" to show only free products |
sort | string | No | "relevance" | Sort order: relevance, newest, rating, price-asc, price-desc |
slug | string | No | -- | Exact slug lookup (returns 0 or 1 result) |
limit | integer | No | 20 | Results per page (max 50) |
offset | integer | No | 0 | Pagination offset |
Response
{
"products": [
{
"id": "abc123",
"title": "Code Review Skill",
"slug": "code-review-skill",
"description": "Automated code review for Claude Code",
"readme": "# Code Review Skill\n...",
"license": "MIT",
"category": "skills",
"price": 0,
"authorUid": "uid_abc",
"authorUsername": "johndoe",
"authorAvatar": "https://...",
"authorDisplayName": "John Doe",
"fileName": "code-review-skill.zip",
"fileSize": 24576,
"thumbnailUrl": "",
"status": "published",
"stats": {
"downloads": 142,
"likes": 37,
"rating": 4.5,
"reviewsCount": 12
},
"tags": ["code-review", "automation"],
"createdAt": "2026-03-20T10:30:00.000Z",
"updatedAt": "2026-03-22T14:15:00.000Z"
}
],
"total": 1,
"limit": 20,
"offset": 0
}Errors
| Status | Condition |
|---|---|
| 429 | Rate limit exceeded (30/min) |
Upload a product file
Request a presigned upload URL for R2 storage. The client uploads the file directly to the returned URL via PUT.
POST /products/uploadAuth: Required (Bearer token) Rate limit: 10/min (strict)
Request body
{
"fileName": "my-skill.zip",
"fileSize": 1048576,
"productKey": "my-skill"
}| Field | Type | Required | Description |
|---|---|---|---|
fileName | string | Yes | File name. Non-alphanumeric chars (except ., -, _) are sanitized. |
fileSize | number | Yes | File size in bytes. Max 50 MB (52,428,800 bytes). |
productKey | string | Yes | Unique key for the product (typically the slug). Sanitized to alphanumeric + _-. |
Response
{
"url": "https://r2-presigned-upload-url...",
"key": "products/my-skill/my-skill.zip"
}The key value is used as fileKey when creating the product via /cli/products/create or as storagePath when scanning via /products/scan.
Errors
| Status | Condition |
|---|---|
| 400 | Missing fields, invalid file size, or file exceeds 50 MB |
| 401 | Missing or invalid token |
| 403 | User is banned |
Scan uploaded content
Scan a ZIP file in storage for content policy violations before publishing. The server downloads the file from R2, inspects it, and returns scan results. Files that fail the scan are automatically deleted from storage.
POST /products/scanAuth: Required (Bearer token) Rate limit: 10/min (strict)
Request body
{
"storagePath": "products/my-skill/my-skill.zip"
}| Field | Type | Required | Description |
|---|---|---|---|
storagePath | string | Yes | R2 storage key. Must start with products/ and end with .zip. |
Response (safe)
{
"safe": true,
"fileTypes": [".ts", ".md", ".json"],
"totalFiles": 12
}Response (violation)
{
"error": "Content policy violation",
"issues": ["Executable binary detected in archive"]
}Status: 400
Errors
| Status | Condition |
|---|---|
| 400 | Invalid path, not a ZIP, file too large (>50 MB), invalid ZIP, or content violation |
| 403 | Ownership check failed (file belongs to another user's product) |
| 404 | File not found in storage |
Toggle like
Toggle a like on a published product. If the user has already liked the product, the like is removed. Uses a Firestore transaction for atomic counter updates.
POST /products/likeAuth: Required (Bearer token) Rate limit: 30/min (strict)
Request body
{
"productId": "abc123"
}Response
{
"liked": true
}liked: true means the like was added. liked: false means it was removed (toggled off).
Errors
| Status | Condition |
|---|---|
| 400 | Missing productId |
| 404 | Product not found or not published |
Get reviews
Retrieve reviews for a product, ordered by most recent first.
GET /products/reviews?productId=abc123Auth: None required Rate limit: 30/min (standard)
Query parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
productId | string | Yes | The product ID |
Response
{
"reviews": [
{
"id": "uid_reviewer",
"authorUid": "uid_reviewer",
"authorUsername": "janedoe",
"authorAvatar": "https://...",
"authorDisplayName": "Jane Doe",
"rating": 5,
"comment": "Excellent skill, saves me hours every week.",
"createdAt": "2026-03-21T08:00:00.000Z"
}
]
}Returns up to 50 reviews per request.
Submit a review
Submit a review for a product. One review per user per product, enforced at the database level.
POST /products/reviewsAuth: Required (Bearer token) Rate limit: 5/min (strict)
Prerequisites
- Paid products: The user must have a completed purchase order.
- Free products: The user must have a server-tracked download record.
- The user cannot review their own product.
Request body
{
"productId": "abc123",
"rating": 5,
"comment": "Excellent skill, saves me hours every week."
}| Field | Type | Required | Constraints |
|---|---|---|---|
productId | string | Yes | Must be a valid product ID |
rating | number | Yes | Integer 1--5 |
comment | string | Yes | 3--1000 characters |
Response
{
"success": true,
"rating": 4.5,
"reviewsCount": 13
}rating is the new average rating for the product. reviewsCount is the updated total.
Errors
| Status | Condition |
|---|---|
| 400 | Invalid rating or comment length |
| 403 | Cannot review own product, purchase required (paid), or download required (free) |
| 404 | Product not found |
| 409 | Already reviewed this product |
Self-approve product
Verified users (email confirmed) can publish their own pending_review products. Accepts a single product ID or "all" to batch-approve all pending products.
POST /products/self-approveAuth: Required (Bearer token, email must be verified) Rate limit: 20/min (strict)
Request body (single)
{
"productId": "abc123"
}Request body (batch)
{
"productId": "all"
}Response
{
"approved": 1
}approved is the count of products moved from pending_review to published.
Errors
| Status | Condition |
|---|---|
| 400 | Missing productId, or product is not in pending_review status |
| 403 | Email not verified, or product belongs to another user |
| 404 | Product not found |
Create or update product (CLI)
Create a new product listing or update an existing one. If the slug already belongs to the same author, the product is updated (version bump). If the slug belongs to a different author, returns 409.
The server runs a mandatory content scan on the uploaded file before creating the product. Rejected files are deleted from storage.
POST /cli/products/createAuth: Required (Bearer token) Rate limit: 5/min (strict)
Request body
{
"title": "Code Review Skill",
"slug": "code-review-skill",
"description": "Automated code review for Claude Code projects",
"category": "skills",
"price": 0,
"license": "MIT",
"tags": ["code-review", "automation"],
"fileKey": "products/code-review-skill/code-review-skill.zip",
"fileName": "code-review-skill.zip",
"fileSize": 24576,
"version": "1.0.0",
"readme": "# Code Review Skill\n\nThis skill provides...",
"source": "cli"
}| Field | Type | Required | Constraints |
|---|---|---|---|
title | string | Yes | Max 100 characters |
slug | string | Yes | Lowercase alphanumeric, hyphens, underscores. 1--50 chars. Must match /^[a-z0-9][a-z0-9_-]*[a-z0-9]$/ |
description | string | Yes | Max 500 characters |
category | string | Yes | One of: skills, squads, agents, workflows, design-systems, claude-md, prompts, applications, systems |
price | number | Yes | 0--9999 USD |
fileKey | string | Yes | R2 storage key from upload step |
fileName | string | Yes | Original file name |
fileSize | number | Yes | File size in bytes (max 50 MB) |
version | string | Yes | Semver format (e.g. 1.0.0) |
license | string | No | Default: MIT. Valid: MIT, Apache-2.0, GPL-3.0, BSD-3-Clause, ISC, CC-BY-4.0, CC0-1.0, Proprietary, Custom, and others |
tags | string[] | No | Max 10 tags, each max 30 characters |
readme | string | No | Markdown content (max 100 KB) |
source | string | No | "cli" or "web" |
Optional enrichment fields
| Field | Type | Default | Description |
|---|---|---|---|
mcsLevel | number | 1 | MCS compatibility level (1, 2, or 3) |
language | string | "en" | Content language (max 10 chars) |
longDescription | string | "" | Extended description (max 2000 chars) |
installTarget | string | "" | Installation target path (max 200 chars, no path traversal) |
compatibility | object | { claudeCode: ">=1.0.0" } | Compatibility requirements |
dependencies | object | { myclaude: [] } | Dependency list (max 20 entries) |
Response (new product)
{
"productId": "xyz789",
"slug": "code-review-skill",
"status": "pending_review",
"updated": false
}Response (updated product)
{
"productId": "abc123",
"slug": "code-review-skill",
"status": "pending_review",
"updated": true
}New products start with status: "pending_review". Use self-approve to publish.
Errors
| Status | Condition |
|---|---|
| 400 | Validation failure, file not found, file too large, invalid ZIP, or content scan failure |
| 409 | Slug is taken by another author |
Report content
Report a product, review, or user for policy violations.
POST /reportsAuth: Required (Bearer token) Rate limit: 10/min (strict), plus max 5 reports per 24 hours per user
Request body
{
"targetType": "product",
"targetId": "abc123",
"reason": "malicious_content",
"description": "Contains obfuscated code that exfiltrates environment variables."
}| Field | Type | Required | Constraints |
|---|---|---|---|
targetType | string | Yes | "product", "review", or "user" |
targetId | string | Yes | ID of the target resource |
reason | string | Yes | "malicious_content", "spam", "inappropriate", "copyright", "other" |
description | string | No | Additional context (max 1000 chars) |
Response
{
"success": true
}Errors
| Status | Condition |
|---|---|
| 400 | Invalid targetType, missing targetId, or invalid reason |
| 409 | Already reported this target |
| 429 | More than 5 reports in the last 24 hours |
Related pages
- API Overview -- Auth model, rate limits, error format
- Downloads API -- Signed URL download flow
- Payments API -- Checkout and purchase flow
- Publishing Guide -- End-to-end publishing walkthrough
- CLI Commands -- CLI command reference