UQL Introduction
UQL is the one query model Nexus uses to read and write across every connected system — databases, ERPs, and SaaS APIs — with a single request shape, capability-aware pushdown, and honest, declared limits.
UQL (Unified Query Language) is the single, structured way to read and write data across every system Nexus connects to. Whether the system behind a query is SQL Server, a NetSuite ERP, QuickBooks, Monday.com, or a generic REST API, you express the request the same way and get results in the same shape. This page is the hub for the UQL model — written for data engineers, integration developers, and the AI agents that increasingly drive these queries.
Why one query model matters
Most integration work means learning a different dialect for every system: T-SQL here, SuiteQL there, a QuickBooks query grammar, a Monday.com GraphQL ItemsQuery, an ad-hoc REST filter syntax somewhere else. Each has its own operators, its own paging, its own idea of what a "table" is. That cost compounds: every new source is a new dialect for every consumer — your reports, your pipelines, your dashboards, and any agent acting on your behalf.
UQL removes that. You describe what you want — a collection, some fields, a filter, a row limit — as a small structured object. Nexus translates that intent into the native call each system understands, executes it as close to the source as the source allows, and returns a flat array of records. Add a new system and your queries keep the same shape; only the bridge name changes.
{
"bridge": "NETSUITE.production",
"collection": "transaction",
"select": ["tranid", "trandate", "amount", "status"],
"where": { "status": { "$eq": "open" }, "amount": { "$gt": 10000 } },
"order_by": [{ "field": "trandate", "direction": "desc" }],
"limit": 100
}
The same request shape works against a database table, a database view, a NetSuite record type, or a report endpoint. The caller does not need to know which.
Why this matters most for AI agents
An AI agent connected through the Nexus MCP server does not get a separate tool per system. It gets one small set of tools — list_bridges, discover_schema, query_data — and one operator vocabulary. That means:
- The agent learns the model once. A filter it composes for a SQL Server table is structurally the filter it composes for a NetSuite record type. There is no per-system prompt engineering.
- Discovery is machine-readable.
discover_schemareturns every container, collection, and field in one normalized shape, so an agent can plan a query against a system it has never seen without guessing table or column names. - The blast radius is bounded by identity, not by tooling. Every UQL call an agent makes runs under the connecting user's RBAC and the same credential-isolation boundary as a human request (see Roles & RBAC). The uniform model does not widen access; it narrows the surface an agent can get wrong.
The bridge and collection model
UQL organizes every source into a small, predictable hierarchy so you can browse and query any system without prior knowledge of its schema.
| Concept | What it is |
|---|---|
| Bridge | A configured connection to one external system, including its credentials. Addressed as SYSTEM.name — e.g. MSSQL.production, NETSUITE.sb, MONDAY.projects. You pick one bridge per query. |
| Container | A top-level grouping inside a system — a database, a schema, a subsidiary, a Monday board. Some systems have one; some have many. |
| Collection | A queryable data set: a table, a view, a record type, or a report endpoint. This is what you read from and write to. |
| Field | A column or property within a collection, with a name, a type, and a writable flag. |
Because every source is normalized to this same vocabulary, a query builder, a saved report, an Excel formula, or an AI agent can work against any connected system uniformly. Credentials never appear in a query — a bridge resolves them server-side, and a per-user bridge's credentials can never resolve for another user (see Connections & bridges).
Discover, then query
UQL read-side work is two operations.
- Discover enumerates everything queryable in a system — all containers, collections, and fields — in one normalized schema. Call it once to learn what a connection can see; the result powers field pickers, query builders, and agent context.
- Read runs a query against a chosen collection and returns rows as flat records, regardless of how the underlying system formatted them.
A discover result is shaped consistently across every connector:
{
"containers": [
{ "id": "default", "name": "Finance ERP", "type": "account" }
],
"collections": [
{ "id": "transaction", "name": "Transactions", "type": "table", "container_id": "default" },
{ "id": "trial_balance", "name": "Trial Balance", "type": "view", "container_id": "default", "read_only": true }
],
"fields": [
{ "id": "amount", "name": "Amount", "collection_id": "transaction", "type": "decimal", "writable": true }
]
}
Always discover before you query. Collection IDs and field names must match exactly what discovery returns — they are not guessed. On some APIs a typo in a field name is not an error; it is a filter the source silently ignores. Discovery removes that class of mistake.
Reading and writing
Reads are available on every connector. Writes — insert, update, delete, and where the system supports it, upsert/merge — are available on connectors whose underlying system allows them, and are additionally gated two ways:
- By RBAC. Writes require a write-scoped grant; reads require a reader grant. The same rules apply to a human, an app token, and an AI agent.
- By declared writability. Each connector declares what it can write, and each collection declares whether it is writable at all. Views are blocked; read-only collections, formula/lookup columns, and computed fields are rejected before any call leaves Nexus — so you learn a limitation up front, not mid-run.
The full request shapes for reads and every write operation are in UQL Syntax.
Pushdown: work runs at the source — when it safely can
UQL is built for pushdown: filters, field selection, sorting, row limits, and aggregation are sent to the source system and executed there, so only the rows you asked for travel back to Nexus. But pushdown is not assumed — it is declared and verified per adapter.
Each connector declares a typed capability profile: which of where, order, group_by, distinct, having, joins, and which aggregate functions it can execute natively. The engine reads that profile and decides, per query, what to hand to the source and what to finish itself:
- A full-pushdown SQL source (PostgreSQL, MySQL, SQL Server, the internal workspace) executes the whole query natively.
- A partial-pushdown source (for example a REST record surface with no filter grammar) gets what it can run; the engine then re-applies the filter in memory so results are still correct, bounded by a post-process row budget.
This is the single most important thing to understand about UQL's honesty: an operator or aggregation a source cannot run does not silently return wrong data. It falls back to a clean in-engine evaluation. The full mechanics — and exactly what each adapter pushes — are in Pushdown & capability semantics.
Where you write UQL
The same request shape runs on three surfaces:
- Ad-hoc, over MCP —
query_datafor a one-off read or write from an agent or the desktop. Defaultlimitis 100 (max 100,000), with a response byte cap to protect an agent's context window. - Saved reports — a named, parameterized UQL query (or several, joined) you run on demand or share. Reports add
$exprand multi-query composition. See Reports. - Pipelines — Python modules where
await ctx.adapters.<bridge>.read({...})and.write({...})combine reads and writes across systems into scheduled, multi-step flows. See Pipelines.
What UQL is not
Being honest about the boundaries is part of the model:
- It is not a cross-source join engine. One UQL read targets one bridge. To combine data from two systems, read each and stage the results — the internal workspace is built for exactly this. Nexus will push a join within a single SQL bridge, but it will not invent a distributed join across bridges.
- It is not raw SQL. Filters are a structured operator set, always parameterized, never string-concatenated. The one exception,
$expr, is a deliberately trusted-author surface available in reports and pipelines — not in ad-hocquery_data— and it is documented as such. - It does not hide capability differences — it declares them. Two connectors that both "support UQL" may push down very different amounts of work. The capability profile is inspectable so you know before you depend on it, rather than discovering it in production.
Where to go next
- UQL Syntax — the full read and write request shapes.
- Filter Operators Reference — the definitive operator catalog and per-connector support.
- Pushdown & Capability Semantics — how capabilities are declared, pushed, and degraded safely.
- UQL Examples — a copy-paste cookbook.
- Connectors Overview — the catalog of systems UQL can reach.