All API routes are in apps/web/app/api/. They use Next.js App Router conventions with NextRequest and NextResponse.
All errors follow this format:
{
"error": "Human-readable message",
"code": "ERROR_CODE"
}| Code | Meaning |
|---|---|
200 |
Success |
201 |
Created |
400 |
Validation error |
401 |
Missing or invalid API key |
404 |
Resource not found |
409 |
Conflict (slug collision, version conflict) |
429 |
Rate limited (OpenRouter) |
500 |
Internal server error |
502 |
Upstream error (OpenRouter) |
GET /api/skills
Query parameters:
search-- Filter by name, description, or tagsstatus-- Filter bydraft,ready, ordeployedpage-- Page number (default: 1)limit-- Items per page (default: 12)sort-- Sort field:updated_at,name,created_atorder-- Sort direction:asc,desc
POST /api/skills
Content-Type: application/json
{
"name": "PR Reviewer",
"description": "Reviews pull requests",
"trigger": "When user asks to review a PR",
"tags": ["review", "pr"],
"content": "## Instructions\n..."
}
GET /api/skills/[id]
PUT /api/skills/[id]
Content-Type: application/json
{
"name": "Updated Name",
"description": "Updated description",
...
}
DELETE /api/skills/[id]
Cascades to skill_files, skill_versions, and test_runs.
POST /api/chat
Content-Type: application/json
{
"messages": [{ "role": "user", "content": "Create a skill that..." }],
"model": "anthropic/claude-sonnet-4"
}
Returns a streaming response (Server-Sent Events) via Vercel AI SDK's toDataStreamResponse(). Used with the useChat() hook on the client.
POST /api/test
Content-Type: application/json
{
"skillId": "abc123",
"model": "anthropic/claude-sonnet-4",
"userMessage": "Review this PR",
"arguments": { "FILE_PATH": "/src/index.ts" }
}
Returns a streaming response. On completion, a test_runs row is saved with token usage and latency metrics.
POST /api/export
Content-Type: application/json
{
"skillId": "abc123"
}
Returns a .zip file download containing the skill directory structure.
POST /api/export/deploy
Content-Type: application/json
{
"skillId": "abc123"
}
Writes the skill to ~/.claude/skills/<slug>/. Returns the deployed path.
POST /api/import
Content-Type: multipart/form-data
# Upload a .zip file, or:
Content-Type: application/json
{
"directory": "~/.claude/skills/"
}
Returns a list of discovered skills with validation results.
GET /api/settings
Returns app settings. The API key value is never included in the response.
PUT /api/settings
Content-Type: application/json
{
"openrouterApiKey": "sk-or-...",
"defaultModel": "anthropic/claude-sonnet-4",
"theme": "dark"
}
The API key is encrypted before storage.
GET /api/models
Returns available AI models from OpenRouter. Results are cached in the database.