Routes
Routes define your API endpoints and their behavior.
Basic Route
{
"path": "/users",
"method": "get",
"description": "Get all users",
"tags": ["Users"]
}Route Properties
| Property | Type | Required | Description |
|---|---|---|---|
path | string | Yes | Endpoint path |
method | string | Yes | get, post, put, patch, delete |
description | string | No | OpenAPI description |
tags | string[] | No | OpenAPI grouping |
requireAuth | boolean | No | Require JWT auth |
authProvider | string | No | jwt or firebase |
requiredPermissions | string[] | No | RBAC permissions |
supabaseQueries | Query[] | No | Database operations |
integrations | Integration[] | No | External service calls |
workflow | Workflow | No | Execute workflow |
cache | CacheConfig | No | Response caching |
rateLimit | RateLimitConfig | No | Rate limiting |
excludeFields | string[] | No | Fields to exclude from read responses and write inputs |
Exclude Fields
Strip sensitive or internal fields from API operations at the database query layer. Applies to both read (response filtering) and write (input sanitization) operations.
Route-Level Exclusion
Apply to all queries in a route:
{
"path": "/users",
"method": "get",
"excludeFields": ["password_hash", "internal_notes", "api_key"],
"supabaseQueries": [{
"table": "users",
"operation": "select"
}]
}Query-Level Exclusion
Apply to specific queries (merged with route-level):
{
"path": "/users/:id",
"method": "get",
"excludeFields": ["password_hash"],
"firebaseQueries": [{
"collection": "users",
"operation": "get",
"docId": "{{params.id}}",
"excludeFields": ["internal_notes", "api_secret"]
}]
}Both levels are merged - the query above excludes password_hash, internal_notes, and api_secret.
Behavior
| Operation | Effect |
|---|---|
select / get / query | Removes fields from response data at database layer |
insert / create / add | Strips fields from request body before insert |
update / set | Strips fields from request body before update |
upsert | Strips fields from request body before upsert |
delete | No effect (no data payload) |
Admin/Dashboard Bypass
Admin users and tenant users accessing via dashboard (with x-tenant-id header) bypass field exclusion and see full data.
| User Type | Behavior |
|---|---|
| Regular user | Fields excluded |
Admin (role: admin) | Full access |
Tenant via dashboard (x-tenant-id header) | Full access |
Use Cases
Prevent reading sensitive data:
{
"path": "/users/:id",
"method": "get",
"excludeFields": ["password_hash", "mfa_secret"]
}Prevent clients from setting protected fields:
{
"path": "/users",
"method": "post",
"excludeFields": ["role", "is_admin", "created_at"],
"supabaseQueries": [{
"table": "users",
"operation": "insert",
"data": "{{body}}"
}]
}Per-query granularity:
{
"path": "/dashboard",
"method": "get",
"supabaseQueries": [
{
"table": "users",
"operation": "select",
"excludeFields": ["password_hash"]
},
{
"table": "orders",
"operation": "select",
"excludeFields": ["internal_cost", "margin"]
}
]
}Path Parameters
{
"path": "/users/:id",
"method": "get",
"supabaseQueries": [{
"table": "users",
"filters": [{
"column": "id",
"operator": "eq",
"value": "{{params.id}}"
}],
"single": true
}]
}Multiple Parameters
{
"path": "/teams/:teamId/members/:memberId",
"method": "get",
"supabaseQueries": [{
"table": "team_members",
"filters": [
{ "column": "team_id", "operator": "eq", "value": "{{params.teamId}}" },
{ "column": "user_id", "operator": "eq", "value": "{{params.memberId}}" }
],
"single": true
}]
}Query Parameters
{
"path": "/users/search",
"method": "get",
"supabaseQueries": [{
"table": "users",
"filters": [{
"column": "name",
"operator": "ilike",
"value": "%{{query.q}}%"
}],
"limit": "{{query.limit}}",
"offset": "{{query.offset}}"
}]
}Advanced Filtering
{
"path": "/products",
"method": "get",
"supabaseQueries": [{
"table": "products",
"filters": [
{ "column": "category", "operator": "eq", "value": "{{query.category}}" },
{ "column": "price", "operator": "gte", "value": "{{query.minPrice}}" },
{ "column": "price", "operator": "lte", "value": "{{query.maxPrice}}" },
{ "column": "in_stock", "operator": "eq", "value": true }
],
"order": { "column": "{{query.sortBy}}", "ascending": "{{query.asc}}" }
}]
}Request Body
{
"path": "/users",
"method": "post",
"requireAuth": true,
"supabaseQueries": [{
"table": "users",
"operation": "insert",
"data": {
"name": "{{body.name}}",
"email": "{{body.email}}",
"created_by": "{{auth.sub}}"
},
"select": "*",
"single": true
}]
}Nested Body Data
{
"path": "/orders",
"method": "post",
"supabaseQueries": [{
"table": "orders",
"operation": "insert",
"data": {
"customer_name": "{{body.customer.name}}",
"customer_email": "{{body.customer.email}}",
"shipping_address": "{{body.shipping.address}}",
"shipping_city": "{{body.shipping.city}}",
"total": "{{body.total}}"
}
}]
}Protected Routes
JWT Authentication
{
"path": "/admin/users",
"method": "get",
"requireAuth": true
}Firebase Authentication
{
"path": "/profile",
"method": "get",
"authProvider": "firebase"
}RBAC Permissions
{
"path": "/admin/users",
"method": "delete",
"requireAuth": true,
"requiredPermissions": ["users:delete"]
}Multiple Permissions (AND)
{
"path": "/admin/settings",
"method": "put",
"requireAuth": true,
"requiredPermissions": ["admin:access", "settings:write"]
}Request Validation
{
"path": "/users",
"method": "post",
"request": {
"body": {
"name": { "type": "string", "required": true, "minLength": 2, "maxLength": 100 },
"email": { "type": "string", "format": "email", "required": true },
"age": { "type": "number", "minimum": 0, "maximum": 150 },
"role": { "type": "string", "enum": ["user", "admin", "moderator"] },
"tags": { "type": "array", "items": { "type": "string" } }
},
"params": {
"id": { "type": "string", "format": "uuid" }
},
"query": {
"limit": { "type": "number", "minimum": 1, "maximum": 100 },
"offset": { "type": "number", "minimum": 0 }
}
}
}Response Schema
{
"path": "/users/:id",
"method": "get",
"response": {
"type": "object",
"properties": {
"id": { "type": "string" },
"name": { "type": "string" },
"email": { "type": "string" },
"createdAt": { "type": "string", "format": "date-time" }
}
}
}Caching
{
"path": "/products",
"method": "get",
"cache": {
"enabled": true,
"ttl": 300,
"varyBy": ["query.category", "query.page"]
}
}Rate Limiting
{
"path": "/api/expensive",
"method": "post",
"rateLimit": {
"max": 10,
"windowMs": 60000,
"message": "Too many requests, please wait"
}
}Integration Routes
Call external services directly from routes.
Single Integration
{
"path": "/notify",
"method": "post",
"requireAuth": true,
"integrations": [{
"type": "slack",
"action": "sendMessage",
"params": {
"channel": "#alerts",
"text": "{{body.message}}"
}
}]
}Chained Integrations
Execute multiple integrations in sequence:
{
"path": "/orders",
"method": "post",
"requireAuth": true,
"supabaseQueries": [{
"table": "orders",
"operation": "insert",
"data": {
"user_id": "{{auth.sub}}",
"product_id": "{{body.productId}}",
"amount": "{{body.amount}}"
},
"select": "*",
"single": true
}],
"integrations": [
{
"type": "stripe",
"action": "createPaymentIntent",
"params": {
"amount": "{{body.amount}}",
"currency": "usd",
"metadata": { "orderId": "{{query.0.id}}" }
}
},
{
"type": "sendgrid",
"action": "sendEmail",
"params": {
"to": "{{body.email}}",
"subject": "Order Confirmation",
"html": "<h1>Order #{{query.0.id}}</h1><p>Amount: ${{body.amount}}</p>"
}
},
{
"type": "slack",
"action": "sendMessage",
"params": {
"channel": "#orders",
"text": "New order #{{query.0.id}} - ${{body.amount}}"
}
}
]
}Conditional Integrations
{
"path": "/orders/:id/status",
"method": "put",
"integrations": [{
"type": "sendgrid",
"action": "sendEmail",
"condition": "{{body.status}} === 'shipped'",
"params": {
"to": "{{body.customerEmail}}",
"subject": "Your order has shipped!",
"templateId": "d-shipping-notification"
}
}]
}Routes with Workflows
Execute complex multi-step workflows from routes.
Inline Workflow
{
"path": "/tickets/:id/analyze",
"method": "post",
"requireAuth": true,
"workflow": {
"id": "analyze-ticket-inline",
"steps": [
{
"id": "fetch-ticket",
"type": "integration",
"integration": "jira",
"action": "getIssue",
"params": { "issueKey": "{{params.id}}" }
},
{
"id": "analyze",
"type": "llm",
"prompt": "Analyze this JIRA ticket and provide:\n1. Summary\n2. Priority assessment\n3. Estimated complexity\n4. Suggested next steps\n\nTicket: {{steps.fetch-ticket.result.fields.summary}}\nDescription: {{steps.fetch-ticket.result.fields.description}}",
"model": "claude-sonnet-4-20250514"
},
{
"id": "save-analysis",
"type": "database",
"operation": "insert",
"table": "ticket_analyses",
"data": {
"ticket_id": "{{params.id}}",
"analysis": "{{steps.analyze.result}}",
"analyzed_by": "{{auth.sub}}"
}
}
]
}
}Reference Saved Workflow
{
"path": "/documents/:id/summarize",
"method": "post",
"requireAuth": true,
"workflow": {
"ref": "document-summarizer",
"input": {
"documentId": "{{params.id}}",
"userId": "{{auth.sub}}",
"options": "{{body}}"
}
}
}Workflow with Database + Integrations
{
"path": "/leads",
"method": "post",
"requireAuth": true,
"workflow": {
"id": "process-new-lead",
"steps": [
{
"id": "save-lead",
"type": "database",
"operation": "insert",
"table": "leads",
"data": {
"name": "{{body.name}}",
"email": "{{body.email}}",
"company": "{{body.company}}",
"source": "{{body.source}}"
},
"select": "*",
"single": true
},
{
"id": "enrich",
"type": "integration",
"integration": "clearbit",
"action": "enrichCompany",
"params": { "domain": "{{body.company}}" }
},
{
"id": "score-lead",
"type": "llm",
"prompt": "Score this lead from 1-100 based on:\n- Company: {{body.company}}\n- Enrichment data: {{steps.enrich.result}}\n\nReturn JSON: {\"score\": number, \"reasoning\": string}",
"responseFormat": "json"
},
{
"id": "update-score",
"type": "database",
"operation": "update",
"table": "leads",
"filters": [{ "column": "id", "operator": "eq", "value": "{{steps.save-lead.result.id}}" }],
"data": {
"score": "{{steps.score-lead.result.score}}",
"enrichment_data": "{{steps.enrich.result}}"
}
},
{
"id": "notify-sales",
"type": "integration",
"integration": "slack",
"action": "sendMessage",
"condition": "{{steps.score-lead.result.score}} >= 70",
"params": {
"channel": "#hot-leads",
"text": ":fire: Hot lead: {{body.name}} from {{body.company}} (Score: {{steps.score-lead.result.score}})"
}
},
{
"id": "create-task",
"type": "integration",
"integration": "jira",
"action": "createIssue",
"condition": "{{steps.score-lead.result.score}} >= 50",
"params": {
"projectKey": "SALES",
"summary": "Follow up with {{body.name}} from {{body.company}}",
"description": "Lead score: {{steps.score-lead.result.score}}\n\n{{steps.score-lead.result.reasoning}}",
"issueType": "Task"
}
}
]
}
}Async Workflow Execution
For long-running workflows, execute asynchronously:
{
"path": "/reports/generate",
"method": "post",
"requireAuth": true,
"workflow": {
"ref": "generate-monthly-report",
"async": true,
"input": {
"month": "{{body.month}}",
"year": "{{body.year}}",
"userId": "{{auth.sub}}"
},
"webhookOnComplete": "{{body.callbackUrl}}"
}
}Response:
{
"workflowId": "wf_abc123",
"status": "queued",
"statusUrl": "/agent/workflow/status?workflowId=wf_abc123"
}Advanced Route Patterns
Batch Operations
{
"path": "/users/batch",
"method": "post",
"requireAuth": true,
"requiredPermissions": ["users:batch"],
"supabaseQueries": [{
"table": "users",
"operation": "insert",
"data": "{{body.users}}",
"select": "*"
}]
}Soft Delete
Entities support soft delete by default. When deleted, records are marked with deleted: true and deleted_at timestamp instead of being permanently removed.
Entity Routes (Admin API)
The admin entity routes support soft delete via query parameters:
| Param | Route | Description |
|---|---|---|
?hard=true | DELETE | Force permanent deletion |
?softDelete=false | DELETE | Disable soft delete for this request |
?includeDeleted=true | GET (list/single) | Include soft-deleted records |
Delete (soft by default):
DELETE /admin/entities/users/123
# Returns: { "success": true, "softDeleted": true }Force hard delete:
DELETE /admin/entities/users/123?hard=true
# Returns: { "success": true }List with deleted records:
GET /admin/entities/users?includeDeleted=trueRestore soft-deleted record:
POST /admin/entities/users/123/restore
# Returns: { "success": true, "data": { ... } }Custom Routes with Soft Delete
For custom tenant routes, use the soft delete config:
{
"path": "/users/:id",
"method": "delete",
"requireAuth": true,
"softDelete": {
"enabled": true,
"allowHardDelete": true
},
"supabaseQueries": [{
"table": "users",
"operation": "update",
"data": {
"deleted": true,
"deleted_at": "{{serverTimestamp}}",
"deleted_by": "{{auth.sub}}"
},
"filters": [{ "column": "id", "operator": "eq", "value": "{{params.id}}" }]
}]
}Soft Delete Fields
All entities should include these fields:
| Field | Type | Description |
|---|---|---|
deleted | boolean | false by default |
deleted_at | timestamp | Set when soft deleted, null when active |
Computed Fields
{
"path": "/orders/:id/total",
"method": "get",
"supabaseQueries": [
{
"table": "order_items",
"operation": "select",
"select": "quantity, unit_price",
"filters": [{ "column": "order_id", "operator": "eq", "value": "{{params.id}}" }]
}
],
"transform": {
"total": "{{sum(query.0, 'quantity * unit_price')}}",
"itemCount": "{{query.0.length}}"
}
}File Upload Route
{
"path": "/documents/upload",
"method": "post",
"requireAuth": true,
"multipart": true,
"workflow": {
"id": "process-document",
"steps": [
{
"id": "upload",
"type": "storage",
"action": "upload",
"params": {
"file": "{{body.file}}",
"bucket": "documents",
"path": "{{auth.sub}}/{{body.file.name}}"
}
},
{
"id": "extract-text",
"type": "integration",
"integration": "documentParser",
"action": "extractText",
"params": { "url": "{{steps.upload.result.url}}" }
},
{
"id": "generate-embeddings",
"type": "embeddings",
"action": "store",
"params": {
"document": "{{steps.extract-text.result}}",
"metadata": {
"userId": "{{auth.sub}}",
"fileName": "{{body.file.name}}"
}
}
},
{
"id": "save-record",
"type": "database",
"operation": "insert",
"table": "documents",
"data": {
"user_id": "{{auth.sub}}",
"name": "{{body.file.name}}",
"url": "{{steps.upload.result.url}}",
"embedding_id": "{{steps.generate-embeddings.result.id}}"
}
}
]
}
}Aggregate Queries
{
"path": "/analytics/dashboard",
"method": "get",
"requireAuth": true,
"cache": { "enabled": true, "ttl": 60 },
"supabaseQueries": [
{
"id": "users",
"table": "users",
"operation": "select",
"select": "count()",
"single": true
},
{
"id": "orders",
"table": "orders",
"operation": "select",
"select": "count(), sum(total)",
"filters": [{ "column": "created_at", "operator": "gte", "value": "{{query.startDate}}" }],
"single": true
},
{
"id": "recent",
"table": "orders",
"operation": "select",
"select": "*, users(name)",
"order": { "column": "created_at", "ascending": false },
"limit": 10
}
],
"response": {
"totalUsers": "{{queries.users.count}}",
"totalOrders": "{{queries.orders.count}}",
"totalRevenue": "{{queries.orders.sum}}",
"recentOrders": "{{queries.recent}}"
}
}Webhook Trigger Route
{
"path": "/webhooks/stripe",
"method": "post",
"webhook": {
"provider": "stripe",
"secret": "{{env.STRIPE_WEBHOOK_SECRET}}"
},
"workflow": {
"id": "handle-stripe-webhook",
"steps": [
{
"id": "route",
"type": "switch",
"value": "{{body.type}}",
"cases": {
"payment_intent.succeeded": "handle-payment",
"customer.subscription.created": "handle-subscription",
"customer.subscription.deleted": "handle-cancellation"
},
"default": "log-event"
}
]
}
}Multi-Tenant Routes
Tenant-Scoped Queries
{
"path": "/projects",
"method": "get",
"requireAuth": true,
"supabaseQueries": [{
"table": "projects",
"filters": [
{ "column": "tenant_id", "operator": "eq", "value": "{{tenant.id}}" }
]
}]
}Cross-Tenant Admin Routes
{
"path": "/admin/tenants/:tenantId/users",
"method": "get",
"requireAuth": true,
"requiredPermissions": ["admin:super"],
"supabaseQueries": [{
"table": "users",
"filters": [
{ "column": "tenant_id", "operator": "eq", "value": "{{params.tenantId}}" }
]
}]
}