Base Path /api/tools
GET

List Tools

â–ļ
GET /api/tools

Returns a paginated list of active AI tools with filtering, search, and sorting. Results are cached in KV for 60 seconds.

Query Parameters
ParamTypeDefaultDescription
pagenumber1Page number
limitnumber20Results per page (max 200)
searchstring—Search in name, description
toolTypestring—e.g. general, lawyer, video, real-estate
pricingModelenum—free | freemium | paid | enterprise
featuredboolean—Filter featured tools only
targetIndustrystring—Industry slug e.g. marketing
tagsstring—Comma-separated tags
sortByenumrankrank | createdAt | rating | viewCount | reviewCount | name | startingPrice
sortOrderenumdescasc | desc
Response
200 — Tool list
{
  "success": true,
  "data": {
    "tools": [
      {
        "id": "tool_abc",
        "slug": "my-ai-tool",
        "name": "My AI Tool",
        "singleLineDescription": "AI that does everything",
        "logoUrl": "https://example.com/logo.png",
        "toolType": "general",
        "targetIndustries": ["marketing", "healthcare"],
        "tags": ["ai", "automation"],
        "pricingModel": "freemium",
        "startingPrice": 19,
        "status": "active",
        "featured": false,
        "verified": true,
        "viewCount": 1250,
        "rating": 4.3,
        "reviewCount": 28,
        "rankScore": 312.5,
        "createdAt": "2026-02-01T00:00:00.000Z",
        "user": { "id": "usr_abc", "username": "johndoe", "avatar": "..." }
      }
    ],
    "pagination": { "total": 480, "page": 1, "limit": 20, "totalPages": 24 }
  }
}
GET

Homepage Listing

â–ļ
GET /api/tools/homepage

Returns all four slot layers in a single response for the main site page. Each layer has its own ordering logic. Use the optional type param to scope all layers to a specific toolType — every sub-site (Lawyers, Videos, Real Estate) passes its own type so each gets an independent, cached listing. Results are cached in KV for 1 hour, keyed per type.

Query Parameters
ParamTypeRequiredDescription
typestringoptional Scope all layers to this toolType. e.g. lawyer, video, real-estate. Omit for the general directory (all types).
Layers Returned
KeyMaxOrderDescription
featured12rankScore DESCAdmin-curated tools (featured = true). Immediate KV purge when admin toggles.
promoted5—Paid promoted tools. Always [] until the promotions table is added.
newAndRising12rankScore DESCPublished within the last 30 days, excluding featured. Each item includes daysLive and recencyBonus (+50→+30→+15→+5 weekly step-down).
ranked24 (page 1)rankScore DESCAll remaining active tools, excluding ids already in layers above. Use meta.rankedTotal for pagination.
Response
200 — Homepage layers
{
  "success": true,
  "data": {
    "featured": [
      {
        "id": "tool_abc", "slug": "harvey-ai", "name": "Harvey AI",
        "featured": true, "verified": true, "rankScore": 412.5
      }
    ],
    "promoted": [],
    "newAndRising": [
      {
        "id": "tool_xyz", "slug": "new-tool", "name": "New Tool",
        "publishedAt": "2026-04-23T00:00:00.000Z",
        "daysLive": 4,
        "recencyBonus": 50
      }
    ],
    "ranked": [
      { "id": "tool_def", "slug": "ranked-tool", "rankScore": 198.0 }
    ],
    "meta": {
      "type": "lawyer",
      "rankedTotal": 84,
      "rankedAt": "2026-04-27T06:00:00.000Z"
    }
  }
}
Sub-site Usage
SiteRequest
General directoryGET /api/tools/homepage
AI for LawyersGET /api/tools/homepage?type=lawyer
AI for VideosGET /api/tools/homepage?type=video
AI for Real EstateGET /api/tools/homepage?type=real-estate
â„šī¸ Each type value gets its own independent KV cache entry — type-scoped changes (e.g. a new lawyer tool published) do not invalidate the video or real-estate homepage caches. See Homepage Listing Strategy for the full slot-layer and cache design.
GET

Get Tool by Slug

â–ļ
GET /api/tools/slug/:slug

Returns a single tool's full details including reviews and bookmark count. Increments viewCount once per visitor per day (cookie-based dedup).

Path Parameters
ParamTypeDescription
slugstringTool URL slug
Response
200 — Full tool detail
{
  "success": true,
  "data": {
    "id": "tool_abc",
    "slug": "my-ai-tool",
    "name": "My AI Tool",
    "description": "Full description...",
    "websiteUrl": "https://mytool.com",
    "screenshotUrls": ["https://..."],
    "features": [{ "title": "Feature 1", "description": "..." }],
    "useCases": ["Marketing automation"],
    "pros": "Easy to use, great API",
    "cons": "No mobile app",
    "platform": ["Web", "API"],
    "languages": ["English"],
    "apiAccess": true,
    "typeMetadata": {},
    "reviews": [
      {
        "name":      "John Doe",
        "rating":    5,
        "content":   "Great tool!",
        "createdAt": "2026-04-10T09:00:00.000Z"
      }
    ],
    "_count": { "bookmarks": 42 }
  }
}
GET

Published Tool Slugs

â–ļ
GET /api/tools/slugs

Returns an array of published tool slugs. Used by sub-sites to build sitemaps and pre-render pages.

Query Parameters
ParamTypeDefaultDescription
limitnumber500Max slugs to return (max 500)
typestring—Filter by toolType e.g. lawyer
Response
200 — Slugs
{
  "success": true,
  "data": {
    "slugs": ["my-ai-tool", "another-tool", "best-ai-tool"],
    "total": 3
  }
}
GET

Check Slug Availability

â–ļ
GET /api/tools/check-slug?slug=my-tool
Query Parameters
ParamTypeRequiredDescription
slugstringrequiredSlug to check (lowercase, hyphens only)
Response
200 — Availability
{ "success": true, "data": { "slug": "my-tool", "available": true } }
POST

Track Click

â–ļ
POST /api/tools/:id/click

Increments the tool's click count when a visitor clicks through to the tool's website. No auth required. Fire-and-forget — does not block the response.

Response
200 — Tracked
{ "success": true, "message": "Click tracked." }

Authenticated Endpoints

POST

Create Tool

🔒 Auth Required â–ļ
POST /api/tools

Creates a new tool listing. Requires an active subscription. The plan's max_tools setting limits how many tools a creator can publish.

Request Body
FieldTypeRequiredDescription
namestringrequired2–100 characters
websiteUrlstring (URL)requiredTool's website
toolTypestringrequirede.g. general, lawyer, video, real-estate
pricingModelenumrequiredfree | freemium | paid | enterprise
slugstringoptionalAuto-generated from name if omitted
singleLineDescriptionstringoptionalMax 120 chars — used in cards
descriptionstringoptionalFull description, max 5000 chars
logoUrlstring (URL)optionalTool logo image URL
screenshotUrlsstring[]optionalUp to 10 screenshot URLs
targetIndustriesstring[]optionalUp to 20 industry slugs
tagsstring[]optionalUp to 30 tags
featuresobject[]optional[{ title, description }] — up to 20
useCasesstring[]optionalUp to 20 use case strings
prosstringoptionalMax 2000 chars
consstringoptionalMax 2000 chars
startingPricenumberoptionalStarting price in USD
apiAccessbooleanoptionalDoes the tool have an API?
platformstring[]optionale.g. ["Web", "iOS", "API"]
languagesstring[]optionalSupported languages
typeMetadataobjectoptionalType-specific data (see Video/Lawyer/RE metadata schemas)
statusenumoptionaldraft | active — defaults to draft
Request Example
JSON
{
  "name": "ContractAI Pro",
  "websiteUrl": "https://contractai.io",
  "toolType": "lawyer",
  "pricingModel": "paid",
  "startingPrice": 49,
  "singleLineDescription": "AI-powered contract review in seconds",
  "targetIndustries": ["legal", "corporate"],
  "tags": ["contract", "legal-ai", "review"],
  "status": "active",
  "typeMetadata": {
    "practiceAreas": ["contract-review", "corporate"],
    "functional": ["document-drafting", "contract-analysis"],
    "compliance": {
      "gdpr": true,
      "soc2": true,
      "trainsOnUserData": false
    }
  }
}
Response
201 — Tool created
{
  "success": true,
  "message": "Tool created successfully.",
  "data": { "tool": { /* full tool object */ } }
}
âš ī¸ Returns 402 PLAN_LIMIT_EXCEEDED if the user has reached their plan's tool limit. Requires active subscription — returns 402 without one.
PUT

Update Tool

🔒 Auth Required â–ļ
PUT /api/tools/:id

Updates an existing tool. All fields are optional. Only the tool owner (or an admin) can update a tool. Triggers sub-site revalidation.

Path Parameters
ParamTypeDescription
idstringTool ID (UUID)
Request Body

Same fields as Create Tool — all optional. Omitted fields are not changed.

Response
200 — Updated tool
{ "success": true, "message": "Tool updated successfully.", "data": { "tool": { /* ... */ } } }
DELETE

Delete Tool

🔒 Auth Required â–ļ
DELETE /api/tools/:id

Permanently deletes a tool and all its reviews/bookmarks (cascade). Only the owner or an admin can delete. Triggers sub-site revalidation.

Response
200 — Deleted
{ "success": true, "message": "Tool deleted successfully." }

Reviews & Ratings

POST

Create Review

â–ļ
POST /api/tools/:id/reviews

Submits a review for a tool. No authentication required — anyone can leave a review by providing their name, email, a star rating, and written content. One review per email per tool. Recalculates the tool's aggregate rating and reviewCount after creation.

Path Parameters
ParamTypeDescription
idstringTool UUID
Request Body
FieldTypeRequiredValidation
namestringrequired1 – 100 characters
emailstringrequiredValid email address — used for dedup
ratingnumberrequired1 – 5
contentstringrequired10 – 1000 characters
recaptchaTokenstringrequiredToken from grecaptcha.execute() (v3) or grecaptcha.getResponse() (v2). Verified server-side before saving.
Responses
201 — Review created
{
  "success": true,
  "message": "Review submitted successfully.",
  "data": {
    "review": {
      "name":      "John Doe",
      "rating":    5,
      "content":   "Amazing tool, very intuitive!",
      "createdAt": "2026-04-22T10:00:00.000Z"
    }
  }
}
409 — Email already reviewed this tool
{ "success": false, "message": "You have already reviewed this tool." }
404 — Tool not found or not active
{ "success": false, "errorCode": "TOOL_NOT_FOUND" }
GET

List Reviews

â–ļ
GET /api/tools/:id/reviews

Returns paginated reviews for a tool. Public — no auth required. The tool detail endpoint (GET /slug/:slug) embeds the 10 most recent reviews; use this endpoint for full pagination.

Path Parameters
ParamTypeDescription
idstringTool UUID
Query Parameters
ParamTypeDefaultDescription
pagenumber1Page number (1-based)
limitnumber10Reviews per page (max 50)
Response
🔒 Email visibility: the email field is returned only when the caller is an authenticated admin (role = "admin"). All other callers — public or regular users — receive reviews without the email field.
200 — Public / regular user (email hidden)
{
  "success": true,
  "data": {
    "reviews": [
      {
        "name":      "Jane Smith",
        "rating":    4,
        "content":   "Great for legal drafting!",
        "createdAt": "2026-04-20T08:30:00.000Z"
      }
    ],
    "pagination": { "total": 28, "page": 1, "limit": 10, "totalPages": 3 }
  }
}
200 — Admin caller (email included)
{
  "success": true,
  "data": {
    "reviews": [
      {
        "name":      "Jane Smith",
        "email":     "janesmith@example.com",
        "rating":    4,
        "content":   "Great for legal drafting!",
        "createdAt": "2026-04-20T08:30:00.000Z"
      }
    ],
    "pagination": { "total": 28, "page": 1, "limit": 10, "totalPages": 3 }
  }
}
â„šī¸ The response also sets an X-Total-Count header with the total review count for convenience.
DELETE

Delete Review

🛡 Admin Only â–ļ
DELETE /api/tools/:id/reviews/:reviewId

Deletes a review. Admin only — reviews are submitted anonymously so there is no user ownership to check. Recalculates the tool's aggregate rating and reviewCount from the remaining reviews after deletion.

Path Parameters
ParamTypeDescription
idstringTool UUID
reviewIdstringReview UUID
Responses
200 — Deleted
{ "success": true, "message": "Review deleted." }
404 — Review or tool not found
{ "success": false, "errorCode": "TOOL_NOT_FOUND" }
POST

Rate Tool

🔒 Auth Required â–ļ
POST /api/tools/:id/rate

Submits a star-only rating (1–5) without a written review. Upserts — calling again replaces the previous rating in place. After each upsert the tool's aggregate rating is recalculated from all ToolRating rows for that tool.

â„šī¸ Review vs Rating: POST /:id/reviews requires written content and counts toward reviewCount. POST /:id/rate is a silent star-only signal — it also influences the aggregate rating field but is tracked separately in the ToolRating table.
Path Parameters
ParamTypeDescription
idstringTool UUID
Request Body
FieldTypeRequiredValidation
ratingnumberrequired1 – 5 (integer)
Responses
200 — Rated (or updated)
{ "success": true, "data": { "rating": 4 } }
404 — Tool not found or not active
{ "success": false, "errorCode": "TOOL_NOT_FOUND" }

Engagement

POST

Toggle Upvote

🔒 Auth Required â–ļ
POST /api/tools/:id/upvote

Toggles an upvote on a tool. First call upvotes (increments upvoteCount); calling again removes the upvote (decrements). Both paths trigger a rank-score recalculation.

Path Parameters
ParamTypeDescription
idstringTool UUID
Response
200 — Toggle result
{ "success": true, "data": { "upvoted": true } }
// or { "upvoted": false } when upvote removed
GET

Upvote Status

🔒 Auth Required â–ļ
GET /api/tools/:id/upvote

Returns whether the authenticated user has upvoted this tool, plus the tool's current total upvote count. Use this to render the upvote button in the correct state (filled/unfilled) on page load without fetching the full tool object.

Path Parameters
ParamTypeDescription
idstringTool UUID
Response
200 — Upvote status
{
  "success": true,
  "data": {
    "upvoted":     true,
    "upvoteCount": 142
  }
}
POST

Toggle Bookmark

🔒 Auth Required â–ļ
POST /api/tools/:id/bookmark

Toggles a bookmark on a tool. First call saves the bookmark; calling again removes it. Bookmarked tools appear in the user's saved list.

Path Parameters
ParamTypeDescription
idstringTool UUID
Response
200 — Toggle result
{ "success": true, "data": { "bookmarked": true } }
// or { "bookmarked": false } when removed

Admin Endpoints

GET

Admin List All Tools

🛡 Admin Only â–ļ
GET /api/tools/admin/all

Returns all tools across all statuses (draft, active, archived) with extended admin filters. No caching — always live data.

Extra Admin Query Params (in addition to standard list params)
ParamTypeDescription
statusenumdraft | active | archived
verifiedbooleanFilter by verification status
apiAccessbooleanFilter by API access flag
minPricenumberMinimum starting price
maxPricenumberMaximum starting price
PATCH

Update Tool Status

🛡 Admin Only â–ļ
PATCH /api/tools/:id/status
Request Body
FieldTypeRequiredDescription
statusenumrequireddraft | active | archived
featuredbooleanoptionalMark as featured
verifiedbooleanoptionalMark as verified
POST

Bulk Import Tools

🛡 Admin Only â–ļ
POST /api/tools/bulk

Imports up to 100 tools in a single request. Each tool is processed independently — failures are reported without aborting the batch. Bypasses plan limits.

Request Body
FieldTypeRequiredDescription
toolsobject[]requiredArray of tool objects (1–100). Same schema as Create Tool.
publishImmediatelybooleanoptionalIf true, tools are set to active. Default: true
Response
200 — Batch result
{
  "success": true,
  "data": {
    "summary": { "total": 5, "created": 4, "skipped": 1, "failed": 0 },
    "created": [ /* tool objects */ ],
    "skipped": [{ "name": "Duplicate Tool", "reason": "Duplicate name for this user" }],
    "failed": []
  }
}
PATCH

Bulk Update Tools

🛡 Admin Only â–ļ
PATCH /api/tools/bulk
Request Body
FieldTypeRequiredDescription
toolsobject[]requiredArray of partial tool objects, each must include id (UUID)
Request Example
JSON
{
  "tools": [
    { "id": "tool_abc", "featured": true, "status": "active" },
    { "id": "tool_def", "pricingModel": "freemium" }
  ]
}