Project Routes
Public and user-facing endpoints for projects.
GET /api/projects
List projects with filters and pagination.
Query params:
| Param | Type | Default | Notes |
|---|---|---|---|
status | string | live | live / pending / rejected |
category | string | — | Category slug. Filters by slug membership. |
sphere | string | — | Sphere name. Same idea. |
pricing | string | all | all / free / freemium / paid |
sort | string | newest | Any key from directoryConfig.sortOptions |
page | number | 1 | Page number |
limit | number | 40 | Items per page |
q | string | — | Search query (substring match) |
Response:
{
"data": [ /* array of projects */ ],
"meta": {
"total": 1248,
"page": 1,
"pageSize": 40,
"hasMore": true
}
}GET /api/projects/[slug]
Get a single project. Returns 404 if not live (unless the request is from the owner or an admin).
POST /api/projects
Create a new submission. Requires auth.
Submission form preview:

Body: validated by ProjectSubmissionSchema from lib/validations/schemas.ts:
{
plan: 'standard' | 'premium',
name: string,
slug: string, // auto-generated if empty
url: string,
description: string,
long_description?: string,
categories: string[], // array of category slugs
logo_url?: string,
screenshots?: string[],
pricing_type: 'free' | 'freemium' | 'paid',
// ... more fields
}Response:
- If plan is
premium, returns a Stripe Checkout URL — redirect user there - If plan is
standard, returns the created project ID directly
Rate-limited: submission tier.
PUT /api/projects/[id]
Update an existing project. Requires ownership or admin.
Body mirrors POST — partial updates supported. Status transitions (pending → live) are admin-only.
DELETE /api/projects/[id]
Delete a project. Requires ownership or admin. Soft-delete by default (sets deleted_at); admins can hard-delete via /api/admin/projects/[id].
POST /api/projects/[id]/upvote
Toggle upvote. Body: none. Requires auth.
Rate-limited: voting tier.
Response:
{ "data": { "upvoted": true, "upvote_count": 42 } }GET /api/projects/[id]/comments
List comments on a project. Paginated.
Feature-flagged: returns 404 if comments: false in features.config.ts.
POST /api/projects/[id]/comments
Add a comment. Requires auth. Rate-limited.
POST /api/projects/[id]/ratings
Add or update this user's rating. Requires auth.
Body: { "rating": 1..5 }.
Error examples
// Missing auth
{ "error": "Unauthorized" } // 401
// Tried to edit someone else's project
{ "error": "Forbidden" } // 403
// Feature disabled
{ "error": "Not found" } // 404
// Validation failed
{
"error": "Invalid input",
"details": { "name": { "_errors": ["Required"] } }
} // 400
// Rate limited
{
"error": "Too many requests",
"retryAfter": 60
} // 429