Public HTTP API
Programmatic access to tools, categories, tags, posts, and AI generation via REST
DirStarter ships with a public REST API at /api/v1 for programmatic management of your directory. It's powered by oRPC and @orpc/openapi, so the same routers drive the internal RPC endpoint (/api/rpc), the public REST surface, and your server components.
Sensitive endpoints (metrics, users, reports, ads, media uploads) are marked internal and never appear on the public surface — they stay reachable only through a signed-in dashboard session.
Available resources
Each resource exposes list, create, update, delete, lookup, and duplicate operations where applicable. Tools also include a scheduled listing endpoint.
| Resource | Base path | Notes |
|---|---|---|
| Tools | /api/v1/tools | Filter by status, paginate, sort, duplicate |
| Categories | /api/v1/categories | Full CRUD + lookup |
| Tags | /api/v1/tags | Full CRUD + lookup |
| Posts | /api/v1/posts | Filter by status, paginate, sort |
| AI | /api/v1/ai/* | Requires AI_GATEWAY_API_KEY; returns 501 when unset |
Media uploads are not exposed on the public API. Upload images through the dashboard, or set
faviconUrl and screenshotUrl to publicly accessible URLs when creating or updating records.
API keys
Access is authenticated with per-user API keys, not a shared environment secret. Issue and revoke keys from the API keys page in your dashboard (/app/api-keys, admin only).
When you create a key:
- The full token (prefixed
dk_) is shown once — copy it immediately, it is never displayed again. - Only a short prefix and a SHA-256 hash of the token are stored, so a database dump never exposes usable keys.
- You can set an optional expiry, and revoke any key at any time.
A key acts on behalf of the user who created it and inherits that user's role permissions. Revoking a key, banning its owner, or letting it expire immediately stops it from authenticating.
There is no API_KEY environment variable. The public API is always mounted; an endpoint
simply returns 401 Unauthorized until a valid key is presented.
Authentication
Every request must include the token as a Bearer credential:
Authorization: Bearer dk_xxxxxxxxxxxx…Missing, malformed, expired, or revoked keys return 401 Unauthorized.
Quickstart
# List tools
curl https://your-site.com/api/v1/tools \
-H "Authorization: Bearer $DIRSTARTER_KEY"
# Create a tool (server assigns id)
curl -X POST https://your-site.com/api/v1/tools \
-H "Authorization: Bearer $DIRSTARTER_KEY" \
-H "Content-Type: application/json" \
-d '{ "name": "Example", "websiteUrl": "https://example.com" }'
# Partially update a tool
curl -X PATCH https://your-site.com/api/v1/tools/{id} \
-H "Authorization: Bearer $DIRSTARTER_KEY" \
-H "Content-Type: application/json" \
-d '{ "tagline": "Updated tagline" }'
# Delete tools
curl -X DELETE https://your-site.com/api/v1/tools \
-H "Authorization: Bearer $DIRSTARTER_KEY" \
-H "Content-Type: application/json" \
-d '{ "ids": ["cuid1", "cuid2"] }'OpenAPI spec & interactive docs
The OpenAPI document is the machine-readable contract for the public API. Use it to generate typed clients, Postman collections, MCP tool definitions, or agent integrations without reading router source.
The spec declares a global BearerAuth security scheme (http + bearer, format API Key). Scalar reads that metadata and renders an auth prompt so integrators can store the key once and send it on every Try-it-out request. All data endpoints enforce the same Bearer check server-side.
The docs UI and the spec JSON are reachable without a key so Scalar can load in a browser; every data operation still requires Bearer auth at runtime.
| Endpoint | Description |
|---|---|
GET /api/v1/openapi.json | Generated OpenAPI 3.1 document |
GET /api/v1/docs | Scalar-rendered interactive API reference |
Open /api/v1/docs, click Authentication, enter one of your API keys as the Bearer token, then use Try-it-out on any operation.
curl https://your-site.com/api/v1/openapi.jsonQuery parameters
List endpoints accept pagination, sorting, date filters, and array filters. For array filters (e.g. status on GET /tools and GET /posts), repeat the key for each value per the OpenAPI default. A single scalar is also accepted for convenience.
| Parameter | Description |
|---|---|
page | Page number (default 1) |
perPage | Items per page (default 25, max 100) |
sort | Comma-separated field:asc or field:desc pairs (e.g. createdAt:desc,name:asc) |
from / to | ISO date strings to filter by date range |
name | Text search filter (tools, categories, tags, posts) |
status | Repeatable status filter (tools, posts) |
# Multiple values: repeat the key
curl "https://your-site.com/api/v1/tools?status=Published&status=Scheduled" \
-H "Authorization: Bearer $DIRSTARTER_KEY"
# Single value: scalar is accepted and treated as a one-element array
curl "https://your-site.com/api/v1/tools?status=Published" \
-H "Authorization: Bearer $DIRSTARTER_KEY"
# Sort and paginate
curl "https://your-site.com/api/v1/tools?sort=createdAt:desc&page=2&perPage=50" \
-H "Authorization: Bearer $DIRSTARTER_KEY"Rate limits
Each API key gets its own bucket: 600 requests / hour. When exceeded, the server returns 429 Too Many Requests with a Retry-After header.
Error shape
Errors are JSON with the following structure:
{ "code": "NOT_FOUND", "message": "Tool not found" }| Code | HTTP | When |
|---|---|---|
UNAUTHORIZED | 401 | Missing or invalid API key |
FORBIDDEN | 403 | Authenticated but not allowed |
NOT_FOUND | 404 | Resource or route does not exist |
BAD_REQUEST | 400 | Invalid input (Zod validation failure) |
TOO_MANY_REQUESTS | 429 | Rate limit exceeded |
NOT_IMPLEMENTED | 501 | Feature not configured (e.g. AI gateway) |
INTERNAL_SERVER_ERROR | 500 | Unexpected server error |
Versioning
The API is mounted at /api/v1. Any breaking change will land under a new version prefix; the old version keeps running until explicitly deprecated. The internal RPC endpoint at /api/rpc is unaffected and continues to power the dashboard.
Last updated on