REST API
Production REST API with 30+ endpoints, API key auth, rate limiting, plan gating, and WebSocket support.
Nella exposes a production REST API for programmatic access to search, verification, context tracking, workspace management, and authentication. Built on Express with Supabase auth, Redis rate limiting, and Zod validation.
Base URL
| Environment | URL |
|---|---|
| Production | https://mcp.getnella.dev/api/v1 |
| Local | http://localhost:8080/api/v1 |
Authentication
All API endpoints require an API key via the Authorization header or X-API-Key header:
curl -H "Authorization: Bearer nella_your_key_here" \
https://mcp.getnella.dev/api/v1/workspaces
API keys are prefixed with nella_ and stored as SHA-256 hashes in the database. Each key has scopes that control access:
Default scopes: workspaces:read, search:read, validate:run, context:read
| Scope | Description |
|---|---|
workspaces:read | List and view workspaces |
workspaces:write | Create, update, delete workspaces |
search:read | Execute searches |
validate:run | Run validations |
context:read | Read context entries |
context:write | Write context entries |
auth:read | List API keys and agents |
auth:write | Create/revoke API keys |
admin | Full access (bypasses all scope checks) |
Rate Limiting
Requests are rate-limited per API key using a Redis sliding window (sorted set):
| Window | Default Limit |
|---|---|
| Per minute | 60 |
| Per hour | 1,000 |
| Per day | 10,000 |
Standard rate limit headers are included in every response:
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 58
X-RateLimit-Reset: 1706889600
Fallback
If Redis is unavailable, rate limiting falls back to an in-memory Map. Rate limit failures are
non-blocking — requests are allowed through if the limiter errors.
Endpoints
Health & Status
| Method | Path | Auth | Description |
|---|---|---|---|
GET | /health | — | Liveness probe |
GET | /ready | — | Readiness probe (?deep=true for full check) |
GET | /metrics | — | Prometheus-compatible metrics |
Search
| Method | Path | Scope | Description |
|---|---|---|---|
POST | /api/v1/search | search:read | Hybrid/semantic/lexical search |
POST | /api/v1/search/batch | search:read | Batch up to 20 queries |
POST | /api/v1/verify | search:read | Code verification against index |
Search request:
{
"query": "authentication middleware",
"workspace": "workspace-id",
"limit": 10,
"mode": "hybrid",
"filter": { "language": "typescript", "path": "src/**" }
}
Batch search:
{
"queries": [
{ "query": "auth middleware", "limit": 5 },
{ "query": "rate limiting", "limit": 5 }
]
}
Workspaces
| Method | Path | Scope | Description |
|---|---|---|---|
GET | /api/v1/workspaces | workspaces:read | List with cursor pagination |
POST | /api/v1/workspaces | workspaces:write | Create workspace |
GET | /api/v1/workspaces/:id | workspaces:read | Get by ID |
PATCH | /api/v1/workspaces/:id | workspaces:write | Update config/name |
DELETE | /api/v1/workspaces/:id | workspaces:write | Remove workspace |
POST | /api/v1/workspaces/:id/index | workspaces:write | Trigger indexing (async, returns 202) |
GET | /api/v1/workspaces/:id/index/status | workspaces:read | Index status |
POST | /api/v1/workspaces/:id/sync | workspaces:write | Trigger cloud sync (async, returns 202) |
Context
| Method | Path | Scope | Description |
|---|---|---|---|
GET | /api/v1/context | context:read | Get context entries |
POST | /api/v1/context/assumptions | context:write | Add assumption |
GET | /api/v1/context/assumptions | context:read | Get assumptions status |
GET | /api/v1/context/files/* | context:read | File change history |
GET | /api/v1/context/dependencies | context:read | Dependency diff |
POST | /api/v1/context/changes | context:write | Record file changes |
GET | /api/v1/context/sessions | context:read | Active context sessions |
DELETE | /api/v1/context/sessions/:id | context:write | Delete session |
Validation
| Method | Path | Scope | Description |
|---|---|---|---|
POST | /api/v1/validate/check | validate:run | Pre-flight safety check |
POST | /api/v1/validate/validate | validate:run | Constraint validation |
POST | /api/v1/validate/run | validate:run | Full validation run |
Auth
| Method | Path | Scope | Description |
|---|---|---|---|
POST | /api/v1/auth/keys | auth:write | Create API key |
GET | /api/v1/auth/keys | auth:read | List user’s keys |
DELETE | /api/v1/auth/keys/:id | auth:write | Revoke key (soft delete) |
POST | /api/v1/auth/agents | auth:write | Register agent |
GET | /api/v1/auth/agents | auth:read | List agents |
GET | /api/v1/auth/usage | auth:read | Usage stats (today/month/total) |
Plan Gating
Certain features require specific plans. The middleware checks your plan before allowing the request:
| Feature | Free | Starter | Pro | Team |
|---|---|---|---|---|
| Search tier | Basic | Advanced | Premium | Premium |
| RAG indexing | — | ✓ | Full | Full |
| Code verification | — | — | ✓ | ✓ |
| Custom constraints | — | ✓ | ✓ | ✓ |
| Context tracking | — | ✓ | Full | Full |
| SSO | — | — | — | ✓ |
| Audit logs | — | — | — | ✓ |
Resource limits by plan:
| Resource | Free | Starter | Pro | Team |
|---|---|---|---|---|
| Requests/month | 5K | 25K | 100K | 500K |
| Projects | 1 | 3 | 10 | Unlimited |
| Members | 1 | 1 | 5 | 50 |
| Storage | 50 MB | 250 MB | 2 GB | 10 GB |
| Workspaces | 0 | 1 | 5 | Unlimited |
Self-Hosted
Self-hosted and unlinked API keys bypass plan gates entirely.
Middleware Stack
Every request passes through these middleware layers (in order):
| # | Middleware | Description |
|---|---|---|
| 1 | helmet() | Security headers (CSP, HSTS, etc.) |
| 2 | compression() | Response compression |
| 3 | CORS | Configurable origins with credentials |
| 4 | JSON parser | 5 MB body limit |
| 5 | Request ID | X-Request-Id from header or crypto.randomUUID() |
| 6 | Request logger | Structured JSON with duration tracking |
| 7 | apiKeyAuth | SHA-256 key lookup in Supabase |
| 8 | requireScope() | Scope enforcement per endpoint |
| 9 | createRateLimitMiddleware() | Redis sliding window |
| 10 | requirePlanFeature() | Plan-based feature gating |
| 11 | validateBody/Query/Params() | Zod schema validation |
| 12 | Route handler | Business logic |
| 13 | Error handler | Consistent JSON error envelope |
Error Responses
All errors follow a consistent JSON envelope:
{
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Too many requests. Retry after 12 seconds.",
"details": { "retryAfter": 12 },
"requestId": "req_abc123"
}
}
WebSocket
The API server includes WebSocket support on the same port for real-time events:
ws://localhost:8080/ws
Events include session updates, indexing progress, and context changes.
Job Queue
Long-running operations (indexing, sync) are processed via BullMQ with graceful Redis fallback:
- Indexing triggers return
202 Acceptedimmediately - Job progress is available via WebSocket or status endpoint
- Graceful shutdown drains active jobs with a 10-second timeout
Environment Variables
| Variable | Required | Default | Description |
|---|---|---|---|
PORT | No | 8080 | Server port |
HOST | No | 0.0.0.0 | Bind address |
NODE_ENV | No | development | Environment |
SUPABASE_URL | Yes | — | Supabase project URL |
SUPABASE_SERVICE_ROLE_KEY | Yes | — | Supabase service role key |
REDIS_URL | No | — | Redis for rate limiting + job queue |
DATABASE_URL | No | — | Direct database access |
JWT_SECRET | No | — | JWT signing secret |
API_KEY_SALT | No | — | Additional salt for API key hashing |
VOYAGE_API_KEY | No | — | Voyage AI for embeddings |
OPENAI_API_KEY | No | — | OpenAI for embeddings |
ALLOWED_ORIGINS | No | app.getnella.dev,... | CORS allowed origins |
LOG_LEVEL | No | info | Log verbosity |