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": [
      {
        "id": "rev_abc",
        "content": "Great tool!",
        "rating": 5,
        "createdAt": "2026-03-10T00:00:00.000Z",
        "user": { "id": "usr_xyz", "username": "reviewer" }
      }
    ],
    "_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." }
POST

Create Review

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

Submits a written review with a star rating for a tool. One review per user per tool — returns 409 Conflict if already reviewed.

Request Body
FieldTypeRequiredDescription
contentstringrequiredReview text — 10 to 1000 characters
ratingnumberrequiredInteger 1–5
Response
201 — Review created
{
  "success": true,
  "data": {
    "review": {
      "id": "rev_abc",
      "content": "Amazing tool, very intuitive!",
      "rating": 5,
      "createdAt": "2026-04-22T10:00:00.000Z",
      "user": { "id": "usr_abc", "username": "johndoe" }
    }
  }
}
POST

Toggle Bookmark

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

Toggles a bookmark on a tool. Call again to unbookmark.

Response
200 — Toggle result
{ "success": true, "data": { "bookmarked": true } }
// or { "bookmarked": false } when unbookmarked
POST

Toggle Upvote

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

Toggles an upvote on a tool. Increments or decrements upvoteCount.

Response
200 — Toggle result
{ "success": true, "data": { "upvoted": true } }
POST

Rate Tool

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

Submits a star-only rating (1–5) without a written review. Upserts — calling again updates the existing rating.

Request Body
FieldTypeRequiredDescription
ratingnumberrequiredInteger 1–5
Response
200
{ "success": true, "data": { "rating": 4 } }

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" }
  ]
}