ShapeRuntime

Freestyle Developer Stack

One endpoint. Typed ops. Real business control.

ShapeRuntime gives teams a single API entrypoint with role-aware auth, advanced query options, transactional mutations, and command-based business workflows. Shape sits on top when you want generated API surface from either entity classes or an existing database schema.

  • For AI toolsAGENTS.md + llms.txt
  • AuthorizationMask + Role checks
  • EndpointsREST · OData · MCP · OpenAPI

Why ShapeRuntime

A compact API layer for fast product intelligence.

Single endpoint contract

All operations run through /index.php with a simple op + payload body.

Role-aware masks

Named query masks can require roles so sensitive datasets stay fenced.

Refresh token rotation

Refresh tokens rotate safely, making replay attacks much harder.

Admin control ops

Create users and reset passwords through explicit admin operations.

OData-inspired options

Use per-query orderby, skip, top, count, groupby, and aggregate.

Safe mutations

Insert, update, and delete data using role-based validation and prepared statements.

Business commands

Run domain workflows with op=command, powered by reusable handlers and transactional orchestration.

Validators and hooks

Attach field validators plus before/after hooks to enforce business rules and side effects safely.

Production-proven flow

The stack is already tested end-to-end with a complete Bruno collection.

Polyglot — PHP & .NET

Switch your stack without rewriting your app.

PHP and .NET share the substrate, not just the brand. The same config/shape.json runs on either runtime. The same database schema, manifest, OpenAPI, MCP tools, and JWT tokens come out the other end. Switching is a deployment change, not a rewrite.

What stays the same when you switch

Database schema + auto-detect conventions
Both runtimes read deleted_at, audit_log, users_lookup_column the same way.
config/shape.json
Masks, access map, DSN, OIDC presets — verbatim. No translation step.
?manifest and ?openapi
Byte-compatible JSON. AI agents and SDK generators don't notice the language change.
?mcp server + tool definitions
Same JSON-RPC protocol; Claude Desktop config unchanged.
JWT tokens
Same signing; sessions survive the switch.
REST / OData / generic-mask URLs
Same paths; frontend code unchanged.

Migration in four steps

  1. Install the new runtime alongside the old (same machine or different — doesn't matter).
  2. Point it at the same config/shape.json and the same database. No file changes.
  3. Verify by diffing ?manifest, ?openapi, and a sample query — should be byte-equivalent.
  4. Switch traffic at DNS or load balancer. Reverse to roll back.

No data migration. No API contract change. No client SDK regen. No frontend rewrite. Full migration guide →

Choose Your Way

Start with the build mode that matches your app, not the package diagram.

Most teams choose by starting point. Manual masks is for exact SQL control. Entity-first is for code as source of truth. Database-first is for existing schemas you want to expose quickly.

Mode 1

Manual Masks

Design every mask, validator, hook, and command by hand. That means your app.php file is the contract and the runtime executes it directly with no entity generation step.

  • Exact SQL shape and naming
  • Best for complex joins and tuned analytics
  • AppRouter::run($configPath, 'myapp')
Mode 2

Entity-First

Write entity classes and attributes in PHP, then let Shape generate the repetitive read surface, validators, and metadata.

  • ShapeApp::fromEntities()
  • Best for greenfield apps and stable domain models
  • Runs through Shape on top of ShapeRuntime
Mode 3

Database-First

Point Shape at an existing schema and let it derive the API contract. Best when the database already exists and you need a fast first version.

  • ShapeApp::fromDatabase()
  • Best for legacy or shared schemas
  • Also runs through Shape on top of ShapeRuntime

Manual masks init

$configPath = __DIR__ . '/../config/app.php';
AppRouter::run($configPath, 'myapp');

Your masks, validators, and commands live directly in app.php. You choose every SQL name and every writable surface yourself.

Entity-first init

$config = ShapeApp::fromEntities(
    __DIR__ . '/../entities',
    __DIR__ . '/../config/app.php'
);
ShapeRouter::run($config, 'myapp');

Entity classes become the source of truth, then Shape generates masks, validators, manifest metadata, and OData-style GET reads.

Database-first init

$config = ShapeApp::fromDatabase(
    __DIR__ . '/../config/app.php'
);
ShapeRouter::run($config, 'myapp');

The live schema becomes the input. Shape introspects only tables listed in access — plus keys and FK relationships — so pointing it at a 2000-table legacy DB stays cheap.

Full Shape Library

A full framework with two entry paths: code-first and DB-first.

Shape is the higher-level framework. Instead of hand-writing every mask and validator, you either declare entities and relationships in code or introspect an existing database schema. The framework generates the repetitive read/write contract and still lets you drop into raw SQL for the hard parts.

Architecture underneath

Manual masks map directly to ShapeRuntime. Entity-first and database-first are two ways of using Shape, which sits on top of ShapeRuntime and adds generation, manifest output, OpenAPI, and OData-style GET routes.

Driver support

One config, three databases.

Drop a DSN in env.php — Shape auto-detects the driver and picks the right schema introspection path, identifier quoting, and insert-id strategy. No dialect switches in your application code.

MySQL / MariaDB

Reads INFORMATION_SCHEMA for tables, columns, FKs, and primary keys. Backtick quoting, lastInsertId() on auto-increment columns.

PostgreSQL

Reads information_schema + pg_index for keys, normalizes types (integerint, numericdecimal). Double-quote quoting, INSERT … RETURNING id for serial/identity columns.

SQLite

Reads PRAGMA table_info, PRAGMA foreign_key_list, and sqlite_master. Backtick quoting, native rowid for inserts. Convenient for tests and local dev.

Single generator pipeline

Both modes end in the same generated surface: masks, validators, manifest metadata, and OData-style reads built on top of ShapeRuntime.

Generated CRUD surface

Get table.list, table.get, table.getMany, FK filters, writable masks, and validators without repeating the same config file patterns.

Discoverable API

Expose machine-readable manifest output and OData-style GET routes for teams building admin tools, AI agents, or low-friction internal clients.

Code-first declaration

#[Entity('products')]
#[Access(roles: ['admin', 'user'])]
class Product
{
    #[Id, AutoIncrement]
    public int $id;

    #[Column(type: 'varchar', length: 255)]
    #[Validate('notEmpty')]
    public string $name;

    #[Column(type: 'decimal', precision: 10, scale: 2)]
    #[Validate('positive')]
    public float $price;
}

One entity file becomes the source of truth for read surface, write validation, and metadata.

DB-first or code-first output

products.list
products.get
products.getMany
products
products.insert validator
products.update validator
manifest entity metadata
GET /api.php/products
GET /api.php/products(42)

The framework removes repetitive config in both modes, but you can still add manual masks for joins, aggregates, and unusual workflows.

Combined use

DB-first auto-CRUD plus hand-written masks.

Shape generates the standard surface for tables you list in access. Custom masks live alongside for joins, aggregates, or queries against tables you do not want to expose as full CRUD. Tables that appear in neither are not introspected at all.

config/app.php

return [
    // Auto-CRUD only for these tables
    'access' => [
        'books'   => ['admin', 'user'],
        'authors' => ['admin'],
    ],

    // Custom masks for everything else
    'masks' => [
        'reports.topAuthors' => [
            'sql' => 'SELECT a.name,
                             COUNT(b.id) AS book_count
                      FROM authors a
                      JOIN books b ON b.author_id = a.id
                      GROUP BY a.id
                      ORDER BY book_count DESC
                      LIMIT 10',
            'params' => [],
            'roles'  => ['admin'],
        ],
        'orders.monthly' => [
            'sql' => 'SELECT DATE_FORMAT(created_at, "%Y-%m") AS month,
                             SUM(total) AS revenue
                      FROM orders
                      WHERE created_at >= :since
                      GROUP BY month',
            'params' => ['since' => ['type' => 'string', 'required' => true]],
            'roles'  => ['admin'],
        ],
    ],
];

Resulting API surface

# Auto-generated from access
books.list
books.get
books.getMany
books.byAuthorId
books              ← writable
authors.list
authors.get
authors.getMany
authors            ← writable

# Hand-written masks
reports.topAuthors
orders.monthly

# orders, reports, and any other tables
# in the database are NOT introspected.

Wildcard + admin masks

Open the whole schema, then carve out what stays private.

Use access['*'] to introspect every table in one line, list the few you want excluded, and let the framework keep secret tables (users, refresh_tokens, …) off the auto-CRUD surface entirely. Opt into admin_masks to get safe-column read masks for the secret tables, gated to admin.

config/app.php

return [
    // Every table gets these roles by default
    'access' => [
        '*'      => ['admin', 'user'],
        'orders' => ['admin'],          // per-table override
    ],

    // Extra dev-defined exclusions (on top of secrets)
    'exclude_tables' => ['legacy_audit', 'tmp_import'],

    // Extend the secret-table baseline
    // (users + refresh_tokens are always secret)
    'secret_tables' => ['api_keys'],

    // Opt-in built-in admin masks for secret tables.
    // Sensitive columns (password*, *_hash, *_secret, ...)
    // are stripped automatically.
    'admin_masks' => true,
];

Resulting API surface

# Auto-CRUD for every table except
# excluded + secret
books.list / .get / .getMany / .byAuthorId / books
authors.list / .get / .getMany / authors
reviews.list / .get / .getMany / reviews
orders.list / .get / .getMany / orders   ← admin only
... (every other non-secret table)

# Safe admin reads for secret tables
users.adminList            ← admin role
users.adminGet
refresh_tokens.adminList
refresh_tokens.adminGet
api_keys.adminList
api_keys.adminGet

# Off the API entirely
legacy_audit, tmp_import   ← excluded
users.password_hash, *_secret, *_token
                           ← stripped from admin masks
1

Pick your input

Choose entity classes when code should own the contract, or choose live database introspection when the schema already exists.

2

Choose entry mode

Use ShapeApp::fromEntities() for code-first work or ShapeApp::fromDatabase() for schema-first onboarding. Both feed the same generator pipeline.

3

Serve runtime + docs

ShapeRouter::run() uses the ShapeRuntime engine underneath and adds manifest, OpenAPI, and OData-style GET endpoints on top.

When to stay low-level

Keep ShapeRuntime when you need exact SQL control.

Use the runtime directly for special joins, irregular mask naming, nonstandard read/write contracts, or highly tuned analytics queries that should remain manually curated.

When to move up

Use Shape when the app is metadata-driven.

Choose the full library when CRUD, validation, relationship reads, manifest discovery, and agent-facing metadata should come from either entity metadata or the existing database schema instead of duplicated config.

Built for AI agents

The smallest path to a working backend.

Shape is engineered around one user — an AI agent producing a backend with the fewest possible tokens, retries, and structural mistakes. Every design choice favors clarity over flexibility, convention over configuration, and structure over vigilance.

SQL is the contract

Every readable and writable surface is a named, vetted SQL mask with explicit roles. The runtime accepts no arbitrary SQL — authorization is structural, not middleware.

Database-first by default

Shape introspects the live schema and generates the entire CRUD surface. No entity classes, no migrations, no two-source-of-truth maintenance.

Convention over configuration

users is secret, deleted_at means soft-delete, NOT NULL implies notEmpty, FK columns auto-generate filter masks. The agent overrides only when its app deliberately diverges.

Self-describing at every layer

Manifest, OpenAPI, and ready-to-paste Anthropic/OpenAI tool definitions all derive from the same metadata. Errors carry expected, example, hint — agents recover without re-fetching anything.

Driver-portable

MySQL, PostgreSQL, and SQLite share one code path. Identifier quoting, insert-id strategy, schema introspection, and boolean coercion are absorbed by the runtime.

Token-frugal by design

Every roadmap item is judged against one question: does it remove tokens an AI agent would otherwise spend? Quick-start one-liners, auto-access conventions, and DDL-from-manifest are next.

Lines of code saved, line by line

Estimates assume an AI agent producing the entire backend end-to-end — initial scaffold plus three iterations on the average endpoint, including the rewrites every retry loop drags along. Lines are PHP source the agent would otherwise have to write, review, and re-read; the same code that drives context-window pressure and breaks on every schema change. Conservative figures.

Concern Without Shape With Shape Lines saved Invoicing
(Fakturoid-like, 30 entities)
E-shop
(traditional, 50 entities)
CRM
(typical, 100 tables)
Define a table's API Entity class + repo + controller + validation Add table to access ~280 / entity ~8.4k lines ~14k lines ~28k lines
Soft-delete an entity Hand-roll WHERE, DELETE-as-UPDATE, restore endpoint Add deleted_at TIMESTAMP NULL ~50 / table ~300 lines ~500 lines ~1.25k lines
Validate inputs Per-column rules in code Inferred from NOT NULL / VARCHAR(N) ~24 / entity ~720 lines ~1.2k lines ~2.4k lines
Document for an LLM consumer Hand-write tool schemas ?manifest&format=anthropic-tools ~100 / entity ~3k lines ~5k lines ~10k lines
Recover from a wrong call Read docs, retry, rewrite (full context re-load each loop) expected + example in the error ~50 / rewrite ~1.5k lines ~3k lines ~6k lines
Switch MySQL → PostgreSQL Rewrite dialect-specific SQL Change DSN ~50 (one-time) ~50 lines ~50 lines ~80 lines
Hide sensitive columns from admins Custom view + endpoint + role check 'admin_masks' => true ~80 / secret table ~320 lines ~400 lines ~640 lines
Open everything for prototyping Generate boilerplate per table 'access' => ['*' => ['admin','user']] (see row 1) (counted above) (counted above) (counted above)
Total lines saved per project ~14k lines ~24k lines ~48k lines

The bottom line

Shape is the library an AI picks because it succeeds on the first try.

Read the full case in docs/shape/WHY-SHAPE.md, browse the framework architecture, or see what's next on the roadmap.

Step zero — Tell your AI about Shape

One file, given to your AI tool, and Shape becomes part of its toolkit.

The first move with Shape isn't writing code — it's handing AGENTS.md to your AI. ~5,000 tokens of curated context: every convention, every error envelope, every common mistake, written specifically for agents. After this single onboarding step, your AI writes correct Shape code on the first try — no Laravel-isms, no scaffolding loops, no re-fetching the manifest after every error.

Claude Code · Claude Desktop

Reference the file directly

Paste in the URL or attach the file to your project:

Read https://github.com/urbidata/shape/blob/main/AGENTS.md
and then build me a backend for <your domain>.

Claude reads the file once and applies every Shape convention from that point in the conversation. For deeper integration, mount the Shape MCP server — Claude Desktop then sees every endpoint as a live tool.

ChatGPT · OpenAI · Codex

Paste the contents into the system prompt

Fetch and attach as project context:

curl https://raw.githubusercontent.com/urbidata/shape/main/AGENTS.md
# → paste output as a system message or upload as a project file

Once the file is in the context window, Shape's conventions are part of the model's working memory for the session. The file fits comfortably alongside any project of normal size.

Cursor · Cline · Continue

Drop it into the agent rules folder

Place AGENTS.md in your IDE's agent-rules directory (or paste into Cursor's .cursorrules):

cp AGENTS.md .cursorrules
# Cursor / Cline / Continue all auto-load this on session start

Or use llms.txt — Shape ships one at the repo root. Tools that follow that convention discover the framework automatically.

Plug into Claude Desktop

Mount Shape as a tool, in 60 seconds.

Shape ships a built-in Model Context Protocol server. Drop one block into your Claude Desktop / Cursor / Cline config and the entire backend appears as live tools and resources — same masks, same auth, same self-documenting errors. No SDK to install, no schema to translate.

Local · stdio

For local development

Add to claude_desktop_config.json:

{
  "mcpServers": {
    "myapp": {
      "command": "php",
      "args": [
        "vendor/shape/shape/bin/shape-mcp",
        ".env"
      ]
    }
  }
}

Spawns the PHP MCP server, reads .env, exposes every entity, mask, command, and resource. Default role: admin (overridable via SHAPE_MCP_ROLE).

Hosted · streamable-HTTP

For deployed backends

Same Shape app already serving HTTP also serves MCP at /api.php?mcp:

{
  "mcpServers": {
    "myapp": {
      "url": "https://api.myapp.com/api.php?mcp",
      "headers": {
        "Authorization": "Bearer eyJhbGc..."
      }
    }
  }
}

Auth flows through the same JwtGuard; the role from the JWT drives tools/list filtering. CORS preflight already configured.

The wedge

shape://README.md as a click-attachable resource.

Most MCP servers expose tools and let the LLM guess. Shape exposes a narrated, human-readable overview of the entire backend as an MCP resource: every entity, mask, command, soft-delete column, role, and auth flow, generated live from the schema. Attach it once per session and the agent has the full surface area as context before its first tool call. Postgres MCP, GitHub MCP, Filesystem MCP — none of them give you the whole spec as a first-class resource. Shape can.

Auth-aware tool list

tools/list filters to what the caller's role can actually call. An agent connected as user never sees users_adminList; an agent connected as admin does. The agent never wastes tokens trying calls it'd get 403'd on — the tool list itself encodes the policy.

Self-documenting errors flow through

A failing tool call returns isError: true with Shape's standard envelope: code, message, expected, example, hint. The agent corrects the call without re-fetching the manifest. Every retry-loop saving the HTTP API gives, applied to MCP too.

One source of truth

Tools are derived from the same LlmToolGenerator that powers ?manifest&format=anthropic-tools. Resources come from ManifestGenerator. Tool calls dispatch through BatchExecutor — the same code path as the HTTP API. There is no parallel "MCP layer" with its own bugs.

Every Shape convention respected

Soft-delete tables auto-filter deleted_at IS NULL on reads through MCP too. Secret tables (users, refresh_tokens) stay hidden. Admin masks expose only safe columns. The MCP surface inherits the same security posture the HTTP API enforces — there is no second contract.

Two transports, one protocol class

Stdio for Claude Desktop on a developer's machine. Streamable-HTTP for hosted backends already on the public internet. Both wrap the same transport-agnostic Shape\Mcp\McpServer. Switching from local prototype to production is a config-block change, not a code change.

Discoverability changes everything

Every Claude Desktop user is now a potential Shape user.

Before MCP, Shape was "a framework I have to remember exists." After: every developer who installs Claude Desktop, Cursor, or Cline discovers Shape the moment they need a backend they can talk to. Read the full guide in docs/shape/MCP.md.

Quick Start

Three calls and you are in motion.

1. Get access token

POST /index.php
{
    "op": "auth.token",
    "payload": {
        "email": "user@example.com",
        "password": "yourPassword"
    }
}

Response includes access_token, refresh_token, expires_in and user profile.

2. Execute query + options

POST /index.php
{
    "op": "query",
    "payload": {
        "queries": [{
            "ref": "items",
            "sql": "itemsByOrderId",
            "params": {"order_id": 42},
            "options": {
                "orderby": [{"field":"id","dir":"desc"}],
                "top": 25,
                "skip": 0,
                "count": true
            }
        }]
    }
}
Authorization: Bearer <access_token>

Query executes through masks with role checks and safe option validation.

3. Rotate session

POST /index.php
{
    "op": "auth.refresh",
    "payload": {
        "refresh_token": "<refresh_token>"
    }
}

Refresh returns a new token pair and invalidates the previous refresh token.

Examples

Backend contract, client behavior.

Query example

Client request

POST /index.php
{
    "op": "query",
    "payload": {
        "queries": [{
            "ref": "items",
            "sql": "itemsByOrderId",
            "params": {"order_id": 42},
            "options": {
                "orderby": [{"field": "id", "dir": "desc"}],
                "top": 25,
                "count": true
            }
        }]
    }
}

Server response

{
    "results": [{
        "ref": "items",
        "meta": {"columns": ["id", "product_id"], "rows": 25},
        "rows": [[91, 77], [90, 61]],
        "count": 25,
        "total_count": 63
    }]
}
Client behavior

Render the current page, show total count for pagination, and sort indicator in the table header.

Use case

Order detail screen loading item rows with paging and sorting.

Validation example

Client request

POST /index.php
{
    "op": "mutation",
    "payload": {
        "transaction": false,
        "mutations": [{
            "table": "products",
            "action": "update",
            "ref": "bad_price",
            "filter": {"id": 42},
            "data": {"price": -15}
        }]
    }
}

Server response

{
    "error": {
        "code": "VALIDATION_ERROR",
        "message": "Price must be positive"
    }
}
Client behavior

Keep the form open, highlight the price field, and show the validation message inline without reloading data.

Use case

Back-office product editor preventing invalid pricing before save.

Hook example

Client request

POST /index.php
{
    "op": "mutation",
    "payload": {
        "transaction": false,
        "mutations": [{
            "table": "products",
            "action": "update",
            "ref": "hooked_update",
            "filter": {"id": 42},
            "data": {"price": 109.99, "stock": 12}
        }]
    }
}

Server response

{
    "results": [{
        "ref": "hooked_update",
        "action": "update",
        "table": "products",
        "affected": 1
    }]
}
Server behavior

Configured before-hook can check policy; after-hook can write logs, invalidate cache, or notify another system.

Client behavior

Show success toast and refetch product detail if hooks can affect derived data shown on screen.

Transaction example

Client request

POST /index.php
{
    "op": "mutation",
    "payload": {
        "transaction": true,
        "mutations": [
            {
                "table": "orders",
                "action": "insert",
                "ref": "new_order",
                "data": {"user_id": 1, "total": 199.99, "status": "pending"}
            },
            {
                "table": "order_items",
                "action": "insert",
                "data": {"order_id": 1, "product_id": 42, "quantity": 2, "price": 99.99}
            }
        ]
    }
}

Server response

{
    "results": [
        {"ref": "new_order", "action": "insert", "table": "orders", "affected": 1},
        {"ref": null, "action": "insert", "table": "order_items", "affected": 1}
    ]
}
Client behavior

Treat success as one atomic operation. If the request fails, show one checkout error and do not partially update UI state.

Use case

Create order header and items together during checkout.

Command example

Client request

POST /index.php
{
    "op": "command",
    "payload": {
        "name": "order.process",
        "params": {
            "order_id": 42,
            "payment_method": "credit_card"
        }
    }
}

Server response

{
    "status": 200,
    "order_id": 42,
    "new_status": "processing",
    "mutations_applied": 2
}
Client behavior

Advance the order UI to the next step, refresh status badges, and show a workflow-specific success message.

Use case

Checkout, billing, onboarding, batch shipping, or other domain workflow that is bigger than plain CRUD.

testapp application examples (full capability map)

Capability Client call testapp behavior
Login / session start op=auth.token Stores access + refresh tokens and enters authenticated app shell.
Session renewal op=auth.refresh Rotates token pair in background and keeps user signed in.
Logout op=auth.logout Revokes refresh token and clears local auth state immediately.
Admin user management op=auth.admin.create-user, op=auth.admin.change-password Provisioning screen for managed users with role-gated admin actions.
Role-aware masked query op=query + sql mask + params Data pages load only allowed datasets; forbidden masks show 403 UX.
Advanced query options orderby/skip/top/count/groupby/aggregate Supports pagination, sortable tables, totals, grouped analytics widgets.
CRUD mutation op=mutation + insert/update/delete Form save/delete operations with affected-row feedback.
Atomic multi-step write op=mutation + payload.transaction=true Checkout-like flows either fully succeed or fully fail.
Validation and hooks Server config validators + before/after hooks Inline validation errors in client; server side effects (cache/log/notify) after success.
Business workflows op=command (e.g. order.process) Single action button can execute full domain process with one API call.

Analytics dashboard query

{
                  "op": "query",
                  "payload": {
                    "queries": [{
                      "ref": "sales",
                      "sql": "salesByDay",
                      "options": {
                    "groupby": ["day"],
                    "aggregate": [{"field":"amount","fn":"sum","as":"total_amount"}],
                    "orderby": [{"field":"day","dir":"asc"}]
                      }
                    }]
                  }
                }

Client renders chart series from grouped totals.

Admin provisioning

{
                  "op": "auth.admin.create-user",
                  "payload": {
                    "email": "agent@example.com",
                    "password": "StrongPass123",
                    "role": "user"
                  }
                }

Client updates user management table after 201 response.

Workflow command button

{
                  "op": "command",
                  "payload": {
                    "name": "orders.ship_batch",
                    "params": {
                      "order_ids": [101, 102, 103],
                      "carrier": "dhl"
                    }
                  }
                }

Client shows one progress action for a multi-step backend workflow.

Reference

Endpoint and operations.

Endpoint Method Usage Auth
/index.php GET Health status and API info No
/index.php POST op=auth.token or op=auth.login No
/index.php POST op=auth.refresh No
/index.php POST op=auth.logout No
/index.php POST op=query with masks and options Yes
/index.php POST op=auth.admin.create-user Yes (admin)
/index.php POST op=auth.admin.change-password Yes (admin)
/index.php POST op=mutation (insert/update/delete) Yes
/index.php POST op=command (domain workflows) Yes

Query options (per query)

Option Type Description
orderbyarraySort by one or more fields with asc or desc.
topintMaximum rows returned (current max: 200).
skipintOffset before returning rows.
countboolInclude total_count before paging.
groupbyarrayGroup rows by selected fields.
aggregatearrayAggregate with count|sum|avg|min|max.

Business features

Feature Where Description
Transactionsop=mutationSet payload.transaction=true to make multi-step writes atomic.
ValidatorsConfigRegister field-level validators per table.action to reject invalid business data.
HooksConfigRun before/after hooks for side effects like notifications, logs, or cache invalidation.
Commandsop=commandExecute domain operations like order.process or inventory.adjust.

Common use cases

Use case Pattern
Checkout pipelinecommand(order.process) + transactional stock/order updates
User onboardingcommand(user.register) + validators + profile bootstrap hooks
Inventory synchronizationcommand(inventory.adjust) + after hook to invalidate cache
Bulk fulfillmentcommand(orders.ship_batch) + transaction across multiple order updates

200 Request accepted

201 Resource created

400 Invalid payload

401 Missing or invalid auth

403 Role restriction hit

404 Route unavailable

409 Conflict with current state

500 Server-side fault

Contact

Build with us.

Need onboarding, endpoint access, or integration support? Reach out and we will get you connected fast.