Custom API · Backlog Delay, Eliminated
Business logic is not a backend problem.
It is a conversation.
Describe what you need — in English, to your AI client via MCP, or directly in YAML. Minimal authors the definition, validates it against your live schema, and deploys a versioned, governed REST endpoint instantly. No handler files. No build pipeline. No external service. Your logic runs inside the runtime.
Two ways to author
Ask for it.
Or just write it.
Both paths produce the same governed, versioned endpoint. The path you take depends on how much control you want — and how fast you need it.
Path 1 · NLP → API via MCP
Describe the intent. Get a live endpoint.
Connect Minimal's MCP server to any AI client — Claude, Cursor, or any MCP-compatible tool. Describe what the API should do in plain English. The AI authors, validates, and deploys the definition for you.
Connect the MCP server
Point your AI client at Minimal's MCP endpoint. Schema, definitions, and deploy tools are immediately available.
Describe the intent
"Give me a paginated endpoint for monthly revenue by region, filtered by year." Natural language. No YAML syntax to remember.
MCP authors the definition
The AI generates the YAML, runs validation against your live schema, and resolves any errors before touching deploy.
Endpoint is live
Deploy fires. The governed REST endpoint is ready. Faster than a sprint ticket gets assigned.
From plain English to a live, versioned, governed API endpoint — in a single conversation turn.
Path 2 · Minimal Studio
Author in Studio. Full control.
Write the definition yourself — using the guided form for common patterns, or the raw YAML editor for advanced chains. Validate against your live schema. Deploy with one click.
Open Minimal Studio
Navigate to Custom API and choose guided form or YAML editor.
Author your logic
SQL query, optional Starlark transform, optional Expr rule evaluation. Set permissions, version, and cache behaviour.
Validate
Studio checks YAML for syntactical correctness. On submission, Starlark code is compiled and verified — errors surface before the definition goes live.
Deploy and try
One click deploys the endpoint. The built-in try window lets you call it immediately — pass parameters, inspect the response.
A real endpoint. Authored by Claude.
Authored by Claude + Minimal MCP — from one sentence requirement.
No YAML written by hand. No build step. No deployment pipeline. The conversation was the deployment.
insight/bike-routes-enriched.yaml
Authored by Claude via Minimal MCP · NYC Open Data · ClickHouse
identifiers: org_id: "[redacted]" project_id: "[redacted]" feature_id: "[redacted]" space_id: "[redacted]" user_id: "[redacted]"permission: [ admin, users ]database: type: clickhouse machine: - host: "[redacted]" port: "9000" username: "[redacted]" password: "[redacted]" name: "new_york" timeout_secs: 60 idle_timeout_secs: 900 max_open_connections: 10 max_idle_connections: 1http: uri: /insight/bike-routes-enriched method: get version: "2.0" input_type: jsonlogic: - lark: config code: | borough_names = { 1: "Manhattan", 2: "Bronx", 3: "Brooklyn", 4: "Queens", 5: "Staten Island" } facility_labels = { "I": "Protected path", "II": "Standard lane", "III": "Shared route", "L": "Link connector" } result = { "borough_names": borough_names, "facility_labels": facility_labels } omit: true - sql: | SELECT count(*) AS grand_total, countDistinct(street) AS total_unique_streets, countDistinct(boro) AS borough_count, round(avg(lanecount), 2) AS overall_avg_lanes FROM city_bike_routes WHERE boro IS NOT NULL omit: true - sql: | SELECT boro, count(*) AS total_routes, countDistinct(street) AS unique_streets, round(avg(lanecount), 2) AS avg_lane_count, max(lanecount) AS max_lane_count, min(lanecount) AS min_lane_count, countIf(facilitycl = 'I') AS protected_paths, countIf(facilitycl = 'II') AS standard_lanes, countIf(facilitycl = 'III') AS shared_routes, countIf(facilitycl = 'L') AS link_connectors, countDistinct(bikedir) AS direction_types, countIf(grnwy IS NOT NULL AND grnwy != '') AS greenway_segments FROM city_bike_routes WHERE boro IS NOT NULL GROUP BY boro ORDER BY total_routes DESC omit: true - lark: enriched code: | names = config["borough_names"] labels = config["facility_labels"] totals = step_1[0] if step_1 else {} grand_total = totals.get("grand_total", 1) boroughs = [] best_prot = 0 best_prot_name = "" rank = 0 for row in step_2: rank = rank + 1 code = row.get("boro") routes = row.get("total_routes", 0) pct = round((routes * 100.0) / grand_total, 1) if grand_total else 0 protected = row.get("protected_paths", 0) standard = row.get("standard_lanes", 0) shared = row.get("shared_routes", 0) links = row.get("link_connectors", 0) safety = round((protected * 100.0) / routes, 1) if routes else 0 if safety > best_prot: best_prot = safety best_prot_name = names.get(code, "Unknown") boroughs.append({ "rank": rank, "borough_code": code, "borough_name": names.get(code, "Unknown"), "total_routes": routes, "share_pct": pct, "unique_streets": row.get("unique_streets", 0), "avg_lane_count": row.get("avg_lane_count", 0), "max_lane_count": row.get("max_lane_count", 0), "facility_breakdown": { "protected_paths": protected, "standard_lanes": standard, "shared_routes": shared, "link_connectors": links }, "protection_rate_pct": safety, "greenway_segments": row.get("greenway_segments", 0), "direction_types": row.get("direction_types", 0) }) top_name = boroughs[0]["borough_name"] if boroughs else "" result = { "summary": { "total_routes": grand_total, "total_unique_streets": totals.get("total_unique_streets", 0), "borough_count": totals.get("borough_count", 0), "overall_avg_lanes": totals.get("overall_avg_lanes", 0), "top_borough": top_name, "safest_borough": best_prot_name, "safest_protection_pct": best_prot }, "boroughs": boroughs, "facility_legend": labels } omit: false - expr: headline code: | str(enriched["summary"]["total_routes"]) + " bike routes across " + str(enriched["summary"]["borough_count"]) + " boroughs | Top: " + enriched["summary"]["top_borough"] + " | Safest: " + enriched["summary"]["safest_borough"] + " (" + str(enriched["summary"]["safest_protection_pct"]) + "% protected)" omit: trueconfig: coalesce: false rollback_version: 0.1 cache_result: falselark is the short keyword for Starlark in Minimal's YAML definitions. The language is Starlark — sandboxed, deterministic, Python-like. The keyword is lark — intentionally brief.
How it works
No handler. No build.
The YAML is the endpoint.
A Custom API is a YAML document. It describes the database, the endpoint URI, the logic chain, and the access rules. Deploy it — and it is live. There is no build step because there is nothing to build.
Write your logic in YAML
Author a definition file: endpoint path, HTTP method, permission roles, and a chain of logic steps. Each step is SQL, Starlark, or Expr — or all three in sequence. Reference input parameters with ?:param bindings. Reference prior steps as step_0, step_1, and so on.
Validate before you deploy
Studio checks your YAML for syntactical correctness. On submission, Starlark code is compiled and verified — errors surface before the definition goes live. MCP-native validation means AI tools can author, verify, and deploy definitions in a single turn.
Deploy — endpoint is live instantly
One deploy call. The endpoint appears at /minimal/api/rest/v1/{org}/{project}/{uri} — versioned, governed, audited. No container restarts. No nginx reloads. No deployment pipelines. The definition is the deployment.
Zero downtime updates
A Custom API can be updated with new logic — additional steps, revised SQL, a new Starlark block — without taking the endpoint offline. The new version is live the moment it is deployed. Historical versions are preserved and restorable through Studio.
Promote from dev to production
Copy a definition — or a batch — from one space to another in a single operation. Dev to staging to production: the same YAML, different environments, zero manual re-entry.
The Logic Engine
Three languages. One chain.
Each doing what it does best.
Every logic step produces output. Every subsequent step receives it. Compose the three languages into a pipeline — SQL retrieves, Starlark transforms, Expr governs.
SQL
Retrieve from the database
Standard SQL against any connected database. Full aggregation support — GROUP BY, HAVING, ORDER BY, joins, subqueries. Parameter bindings via ?:name are resolved from request input at runtime.
Starlark
lark in YAMLTransform and compute
Python-like scripting for transformation, filtering, and enrichment. Filter rows, reshape objects, compute derived fields, apply business rules. Receives the output of any prior step. Reusable Starlark modules can be shared across definitions.
Expr
Runtime rules and access control
Common Expression Language (CEL) — small, safe, and embeddable. Evaluates runtime rules, access conditions, and user-defined logic without a deployment. Use it for permission checks, row-level conditions, and business rules that need to run inside the request.
The omit flag
Intermediate steps set omit: true — their output is accessible to later steps but never returned in the API response. Only the final step with omit: false reaches the caller. The chain is computation. The response is intent.
Why Starlark?
Starlark is a sandboxed, deterministic, Python-like language developed at Google and used in production by Bazel and Meta Buck. In Minimal's runtime it operates under strict constraints:
- —No file system access
- —No outbound network calls
- —No side effects outside the database operation defined
- —Deterministic execution — same input always produces same output
- —Every execution is auditable
This is not a limitation. It is a security property. An attacker who compromises a Starlark definition cannot exfiltrate data to an external endpoint, because Starlark cannot make one. For teams under SOC 2, ISO 27001, or internal audit — this is a control requirement, not a footnote.
load loads functions, not modules
When a Starlark definition uses the load command to import shared logic, Minimal's compiler loads only the specific function requested — not the entire module. The runtime carries only what the definition needs. No module bloat. No unused code in memory. Every execution is lean.
Inside the Runtime
No external service. No sidecar.
No cold start.
Every other API platform eventually pushes custom logic out of the runtime and into a separate service: an HTTP Action handler, a Lambda function, an Edge Function, a deployed microservice. That service must be deployed. Scaled. Monitored. It introduces network latency on every call. It can have side effects you did not intend. It accumulates.
Custom API logic runs inside Minimal's runtime — on the same process, with the same access control, in the same audit trail. There is no network hop. There is no cold start. There is no service to deploy. The definition is the endpoint. The runtime is the executor.
Agents are Ferraris. They need a structured highway to accomplish the impossible. Custom API is part of that highway — governed lanes your agents can call via MCP, authored in a single conversation, executed inside a sandboxed runtime that cannot be exploited.
The difference
Other platforms
Logic lives in an external handler
Handler must be deployed and scaled
Network hop on every call
Cold start on first call
Separate service to monitor
Side effects possible
Minimal Custom API
Logic lives inside the runtime
Deploy is a YAML upload
Zero network hop
Zero cold start
Same audit log as every API call
Sandboxed — no side effects outside the DB
Other platforms
Logic lives in an external handler
Handler must be deployed and scaled
Network hop on every call
Cold start on first call
Separate service to monitor
Side effects possible
Minimal Custom API
Logic lives inside the runtime
Deploy is a YAML upload
Zero network hop
Zero cold start
Same audit log as every API call
Sandboxed — no side effects outside the DB
We Complete You
MCP bridges your AI client to Minimal.
The conversation becomes the API.
Your AI client speaks MCP. Minimal speaks MCP. The gap between "I need an endpoint for X" and a live, governed, versioned REST endpoint is one conversation turn. No sprint. No ticket. No YAML written by hand unless you want to.
This is what "we complete you" means in practice. Your AI platform is already capable. Minimal gives it the governed data access layer it needs to accomplish the impossible — and the ability to create new governed endpoints in the same breath.
- ·Author Custom API definitions in plain English via your AI client
- ·Validate against live schema — AI resolves errors before deploy
- ·Deploy from the same conversation — no context switch
- ·Call governed endpoints as MCP tools in the same agent session
- ·The endpoint your agent needs and the endpoint your agent creates — same runtime
Built in — not bolted on