Skip to content

Routes

Routes define your API endpoints and their behavior.

Basic Route

json
{
  "path": "/users",
  "method": "get",
  "description": "Get all users",
  "tags": ["Users"]
}

Route Properties

PropertyTypeRequiredDescription
pathstringYesEndpoint path
methodstringYesget, post, put, patch, delete
descriptionstringNoOpenAPI description
tagsstring[]NoOpenAPI grouping
requireAuthbooleanNoRequire JWT auth
authProviderstringNojwt or firebase
requiredPermissionsstring[]NoRBAC permissions
supabaseQueriesQuery[]NoDatabase operations
integrationsIntegration[]NoExternal service calls
workflowWorkflowNoExecute workflow
cacheCacheConfigNoResponse caching
rateLimitRateLimitConfigNoRate limiting
excludeFieldsstring[]NoFields 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:

json
{
  "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):

json
{
  "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

OperationEffect
select / get / queryRemoves fields from response data at database layer
insert / create / addStrips fields from request body before insert
update / setStrips fields from request body before update
upsertStrips fields from request body before upsert
deleteNo 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 TypeBehavior
Regular userFields excluded
Admin (role: admin)Full access
Tenant via dashboard (x-tenant-id header)Full access

Use Cases

Prevent reading sensitive data:

json
{
  "path": "/users/:id",
  "method": "get",
  "excludeFields": ["password_hash", "mfa_secret"]
}

Prevent clients from setting protected fields:

json
{
  "path": "/users",
  "method": "post",
  "excludeFields": ["role", "is_admin", "created_at"],
  "supabaseQueries": [{
    "table": "users",
    "operation": "insert",
    "data": "{{body}}"
  }]
}

Per-query granularity:

json
{
  "path": "/dashboard",
  "method": "get",
  "supabaseQueries": [
    {
      "table": "users",
      "operation": "select",
      "excludeFields": ["password_hash"]
    },
    {
      "table": "orders",
      "operation": "select",
      "excludeFields": ["internal_cost", "margin"]
    }
  ]
}

Path Parameters

json
{
  "path": "/users/:id",
  "method": "get",
  "supabaseQueries": [{
    "table": "users",
    "filters": [{
      "column": "id",
      "operator": "eq",
      "value": "{{params.id}}"
    }],
    "single": true
  }]
}

Multiple Parameters

json
{
  "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

json
{
  "path": "/users/search",
  "method": "get",
  "supabaseQueries": [{
    "table": "users",
    "filters": [{
      "column": "name",
      "operator": "ilike",
      "value": "%{{query.q}}%"
    }],
    "limit": "{{query.limit}}",
    "offset": "{{query.offset}}"
  }]
}

Advanced Filtering

json
{
  "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

json
{
  "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

json
{
  "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

json
{
  "path": "/admin/users",
  "method": "get",
  "requireAuth": true
}

Firebase Authentication

json
{
  "path": "/profile",
  "method": "get",
  "authProvider": "firebase"
}

RBAC Permissions

json
{
  "path": "/admin/users",
  "method": "delete",
  "requireAuth": true,
  "requiredPermissions": ["users:delete"]
}

Multiple Permissions (AND)

json
{
  "path": "/admin/settings",
  "method": "put",
  "requireAuth": true,
  "requiredPermissions": ["admin:access", "settings:write"]
}

Request Validation

json
{
  "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

json
{
  "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

json
{
  "path": "/products",
  "method": "get",
  "cache": {
    "enabled": true,
    "ttl": 300,
    "varyBy": ["query.category", "query.page"]
  }
}

Rate Limiting

json
{
  "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

json
{
  "path": "/notify",
  "method": "post",
  "requireAuth": true,
  "integrations": [{
    "type": "slack",
    "action": "sendMessage",
    "params": {
      "channel": "#alerts",
      "text": "{{body.message}}"
    }
  }]
}

Chained Integrations

Execute multiple integrations in sequence:

json
{
  "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 #&#123;&#123;query.0.id&#125;&#125;</h1><p>Amount: $&#123;&#123;body.amount&#125;&#125;</p>"
      }
    },
    {
      "type": "slack",
      "action": "sendMessage",
      "params": {
        "channel": "#orders",
        "text": "New order #&#123;&#123;query.0.id&#125;&#125; - $&#123;&#123;body.amount&#125;&#125;"
      }
    }
  ]
}

Conditional Integrations

json
{
  "path": "/orders/:id/status",
  "method": "put",
  "integrations": [{
    "type": "sendgrid",
    "action": "sendEmail",
    "condition": "&#123;&#123;body.status&#125;&#125; === 'shipped'",
    "params": {
      "to": "&#123;&#123;body.customerEmail&#125;&#125;",
      "subject": "Your order has shipped!",
      "templateId": "d-shipping-notification"
    }
  }]
}

Routes with Workflows

Execute complex multi-step workflows from routes.

Inline Workflow

json
{
  "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": "&#123;&#123;params.id&#125;&#125;" }
      },
      {
        "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: &#123;&#123;steps.fetch-ticket.result.fields.summary&#125;&#125;\nDescription: &#123;&#123;steps.fetch-ticket.result.fields.description&#125;&#125;",
        "model": "claude-sonnet-4-20250514"
      },
      {
        "id": "save-analysis",
        "type": "database",
        "operation": "insert",
        "table": "ticket_analyses",
        "data": {
          "ticket_id": "&#123;&#123;params.id&#125;&#125;",
          "analysis": "&#123;&#123;steps.analyze.result&#125;&#125;",
          "analyzed_by": "&#123;&#123;auth.sub&#125;&#125;"
        }
      }
    ]
  }
}

Reference Saved Workflow

json
{
  "path": "/documents/:id/summarize",
  "method": "post",
  "requireAuth": true,
  "workflow": {
    "ref": "document-summarizer",
    "input": {
      "documentId": "&#123;&#123;params.id&#125;&#125;",
      "userId": "&#123;&#123;auth.sub&#125;&#125;",
      "options": "&#123;&#123;body&#125;&#125;"
    }
  }
}

Workflow with Database + Integrations

json
{
  "path": "/leads",
  "method": "post",
  "requireAuth": true,
  "workflow": {
    "id": "process-new-lead",
    "steps": [
      {
        "id": "save-lead",
        "type": "database",
        "operation": "insert",
        "table": "leads",
        "data": {
          "name": "&#123;&#123;body.name&#125;&#125;",
          "email": "&#123;&#123;body.email&#125;&#125;",
          "company": "&#123;&#123;body.company&#125;&#125;",
          "source": "&#123;&#123;body.source&#125;&#125;"
        },
        "select": "*",
        "single": true
      },
      {
        "id": "enrich",
        "type": "integration",
        "integration": "clearbit",
        "action": "enrichCompany",
        "params": { "domain": "&#123;&#123;body.company&#125;&#125;" }
      },
      {
        "id": "score-lead",
        "type": "llm",
        "prompt": "Score this lead from 1-100 based on:\n- Company: &#123;&#123;body.company&#125;&#125;\n- Enrichment data: &#123;&#123;steps.enrich.result&#125;&#125;\n\nReturn JSON: {\"score\": number, \"reasoning\": string}",
        "responseFormat": "json"
      },
      {
        "id": "update-score",
        "type": "database",
        "operation": "update",
        "table": "leads",
        "filters": [{ "column": "id", "operator": "eq", "value": "&#123;&#123;steps.save-lead.result.id&#125;&#125;" }],
        "data": {
          "score": "&#123;&#123;steps.score-lead.result.score&#125;&#125;",
          "enrichment_data": "&#123;&#123;steps.enrich.result&#125;&#125;"
        }
      },
      {
        "id": "notify-sales",
        "type": "integration",
        "integration": "slack",
        "action": "sendMessage",
        "condition": "&#123;&#123;steps.score-lead.result.score&#125;&#125; >= 70",
        "params": {
          "channel": "#hot-leads",
          "text": ":fire: Hot lead: &#123;&#123;body.name&#125;&#125; from &#123;&#123;body.company&#125;&#125; (Score: &#123;&#123;steps.score-lead.result.score&#125;&#125;)"
        }
      },
      {
        "id": "create-task",
        "type": "integration",
        "integration": "jira",
        "action": "createIssue",
        "condition": "&#123;&#123;steps.score-lead.result.score&#125;&#125; >= 50",
        "params": {
          "projectKey": "SALES",
          "summary": "Follow up with &#123;&#123;body.name&#125;&#125; from &#123;&#123;body.company&#125;&#125;",
          "description": "Lead score: &#123;&#123;steps.score-lead.result.score&#125;&#125;\n\n&#123;&#123;steps.score-lead.result.reasoning&#125;&#125;",
          "issueType": "Task"
        }
      }
    ]
  }
}

Async Workflow Execution

For long-running workflows, execute asynchronously:

json
{
  "path": "/reports/generate",
  "method": "post",
  "requireAuth": true,
  "workflow": {
    "ref": "generate-monthly-report",
    "async": true,
    "input": {
      "month": "&#123;&#123;body.month&#125;&#125;",
      "year": "&#123;&#123;body.year&#125;&#125;",
      "userId": "&#123;&#123;auth.sub&#125;&#125;"
    },
    "webhookOnComplete": "&#123;&#123;body.callbackUrl&#125;&#125;"
  }
}

Response:

json
{
  "workflowId": "wf_abc123",
  "status": "queued",
  "statusUrl": "/agent/workflow/status?workflowId=wf_abc123"
}

Advanced Route Patterns

Batch Operations

json
{
  "path": "/users/batch",
  "method": "post",
  "requireAuth": true,
  "requiredPermissions": ["users:batch"],
  "supabaseQueries": [{
    "table": "users",
    "operation": "insert",
    "data": "&#123;&#123;body.users&#125;&#125;",
    "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:

ParamRouteDescription
?hard=trueDELETEForce permanent deletion
?softDelete=falseDELETEDisable soft delete for this request
?includeDeleted=trueGET (list/single)Include soft-deleted records

Delete (soft by default):

bash
DELETE /admin/entities/users/123
# Returns: { "success": true, "softDeleted": true }

Force hard delete:

bash
DELETE /admin/entities/users/123?hard=true
# Returns: { "success": true }

List with deleted records:

bash
GET /admin/entities/users?includeDeleted=true

Restore soft-deleted record:

bash
POST /admin/entities/users/123/restore
# Returns: { "success": true, "data": { ... } }

Custom Routes with Soft Delete

For custom tenant routes, use the soft delete config:

json
{
  "path": "/users/:id",
  "method": "delete",
  "requireAuth": true,
  "softDelete": {
    "enabled": true,
    "allowHardDelete": true
  },
  "supabaseQueries": [{
    "table": "users",
    "operation": "update",
    "data": {
      "deleted": true,
      "deleted_at": "&#123;&#123;serverTimestamp&#125;&#125;",
      "deleted_by": "&#123;&#123;auth.sub&#125;&#125;"
    },
    "filters": [{ "column": "id", "operator": "eq", "value": "&#123;&#123;params.id&#125;&#125;" }]
  }]
}

Soft Delete Fields

All entities should include these fields:

FieldTypeDescription
deletedbooleanfalse by default
deleted_attimestampSet when soft deleted, null when active

Computed Fields

json
{
  "path": "/orders/:id/total",
  "method": "get",
  "supabaseQueries": [
    {
      "table": "order_items",
      "operation": "select",
      "select": "quantity, unit_price",
      "filters": [{ "column": "order_id", "operator": "eq", "value": "&#123;&#123;params.id&#125;&#125;" }]
    }
  ],
  "transform": {
    "total": "&#123;&#123;sum(query.0, 'quantity * unit_price')&#125;&#125;",
    "itemCount": "&#123;&#123;query.0.length&#125;&#125;"
  }
}

File Upload Route

json
{
  "path": "/documents/upload",
  "method": "post",
  "requireAuth": true,
  "multipart": true,
  "workflow": {
    "id": "process-document",
    "steps": [
      {
        "id": "upload",
        "type": "storage",
        "action": "upload",
        "params": {
          "file": "&#123;&#123;body.file&#125;&#125;",
          "bucket": "documents",
          "path": "&#123;&#123;auth.sub&#125;&#125;/&#123;&#123;body.file.name&#125;&#125;"
        }
      },
      {
        "id": "extract-text",
        "type": "integration",
        "integration": "documentParser",
        "action": "extractText",
        "params": { "url": "&#123;&#123;steps.upload.result.url&#125;&#125;" }
      },
      {
        "id": "generate-embeddings",
        "type": "embeddings",
        "action": "store",
        "params": {
          "document": "&#123;&#123;steps.extract-text.result&#125;&#125;",
          "metadata": {
            "userId": "&#123;&#123;auth.sub&#125;&#125;",
            "fileName": "&#123;&#123;body.file.name&#125;&#125;"
          }
        }
      },
      {
        "id": "save-record",
        "type": "database",
        "operation": "insert",
        "table": "documents",
        "data": {
          "user_id": "&#123;&#123;auth.sub&#125;&#125;",
          "name": "&#123;&#123;body.file.name&#125;&#125;",
          "url": "&#123;&#123;steps.upload.result.url&#125;&#125;",
          "embedding_id": "&#123;&#123;steps.generate-embeddings.result.id&#125;&#125;"
        }
      }
    ]
  }
}

Aggregate Queries

json
{
  "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": "&#123;&#123;query.startDate&#125;&#125;" }],
      "single": true
    },
    {
      "id": "recent",
      "table": "orders",
      "operation": "select",
      "select": "*, users(name)",
      "order": { "column": "created_at", "ascending": false },
      "limit": 10
    }
  ],
  "response": {
    "totalUsers": "&#123;&#123;queries.users.count&#125;&#125;",
    "totalOrders": "&#123;&#123;queries.orders.count&#125;&#125;",
    "totalRevenue": "&#123;&#123;queries.orders.sum&#125;&#125;",
    "recentOrders": "&#123;&#123;queries.recent&#125;&#125;"
  }
}

Webhook Trigger Route

json
{
  "path": "/webhooks/stripe",
  "method": "post",
  "webhook": {
    "provider": "stripe",
    "secret": "&#123;&#123;env.STRIPE_WEBHOOK_SECRET&#125;&#125;"
  },
  "workflow": {
    "id": "handle-stripe-webhook",
    "steps": [
      {
        "id": "route",
        "type": "switch",
        "value": "&#123;&#123;body.type&#125;&#125;",
        "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

json
{
  "path": "/projects",
  "method": "get",
  "requireAuth": true,
  "supabaseQueries": [{
    "table": "projects",
    "filters": [
      { "column": "tenant_id", "operator": "eq", "value": "&#123;&#123;tenant.id&#125;&#125;" }
    ]
  }]
}

Cross-Tenant Admin Routes

json
{
  "path": "/admin/tenants/:tenantId/users",
  "method": "get",
  "requireAuth": true,
  "requiredPermissions": ["admin:super"],
  "supabaseQueries": [{
    "table": "users",
    "filters": [
      { "column": "tenant_id", "operator": "eq", "value": "&#123;&#123;params.tenantId&#125;&#125;" }
    ]
  }]
}

Released under the ISC License.