Build on existing work
Share versioned SQL templates across pipelines, and compose a larger workflow from smaller pipelines.
From a question to a reusable answer
A pipeline is the saved recipe for an answer: which sources to read, how to combine them, and which inputs can change. AI helps write it. The server executes it. Your team decides when it is ready to share.
An operator runs datapipelines on your infrastructure and registers the database connections. You choose which workspaces and people can use them.
The agent accesses connections by name. Database passwords stay with the server. Cross-database joins use temporary staging during the run; you do not need to build a warehouse first.
Illustrative names. Use the supported databases your organization already runs.
Connect a compatible AI client through MCP, the protocol it uses to work with the server. The agent reads your schema, probes your data and checks recorded facts before building a pipeline.
Specify what “revenue” includes, which dates matter and what to exclude. The agent can save those definitions for later sessions and reuse SQL templates across pipelines.
“Show revenue by region for the last quarter, excluding cancelled orders.”
Agree on the definition, check source coverage, then record the rule. The next question starts with that context.
Review the SQL, source assumptions and output. Add release checks where they help: counts, expected totals, or other values your team can verify.
Configured checks run on the server before release. A person approves the version. The released logic is immutable; further changes go into a draft. Passing checks support review without proving every business interpretation.
Publish a released, read-only pipeline as an authenticated API. Each request supplies declared inputs and executes the latest released version. Changing a draft does not change what the endpoint runs.
For Tableau, write results into a database it already connects to, or use a suitable web data connector for the API. Native dashboard creation is planned.
GET /api/x/demo/top-company-by-borough?anchor_date=2025-01-01
The demo workspace's published endpoint. Your deployed host, released pipeline and scoped key determine access.
Built to run again
Change the reporting date or region without asking AI to rewrite the query. The saved pipeline executes against your sources; a released version fixes the logic, not a snapshot of the data.
Share versioned SQL templates across pipelines, and compose a larger workflow from smaller pipelines.
Follow execution progress and inspect results, node timings and errors when a run needs attention.
Promote released pipelines and their dependencies between configured environments, with a person initiating the move.
Inside the application
These spaces will show the real pipeline canvas, execution results and release workflow after testing is complete.
Application capture coming after testing
Application capture coming after testing
Application capture coming after testing
Try the sample data first
./app.sh --start --demo nyc,trade,lake
Run this in your own deployment checkout. It starts the full sample stack.
Before you begin, and once you run it
Yes. Someone deploys the server on your infrastructure, registers the database connections in the application (there is deliberately no agent tool that registers one — docs/mcp-server.md §6.2.22) and connects the AI client. After setup, business questions can be asked in plain language; a person who understands your data reviews the resulting SQL and numbers.
The server holds your database credentials and the agent never sees them (docs/datasources.md §7), but the schema, statistics, preview rows and execution results the agent asks for are returned to it (docs/mcp-server.md §6.2.15) — and so may reach the model provider behind your client. Choose the client, the model and the datasources' access to match your requirements.
Not yet. Today authoring is through an AI agent over MCP or the REST API; the browser helps you inspect a pipeline, execute it, read results and errors, and manage draft and released versions (docs/pipeline-editor.md §11). Browser authoring is a roadmap item (docs/ROADMAP.md §2).
A released pipeline gets a URL under /api/x/…; a GET runs it with the query string bound to its declared parameters and returns the rows as JSON. Keys are bound to a path prefix, so a partner can be given one endpoint and nothing else. The serving contract is docs/rest-api.md §19.
For the API half, today: an agent authors the pipeline against your databases, runs it, fixes what fails, you review and release, and the endpoint exists — no deployment, no service to write, no gateway to configure. Dashboards your agent creates and you embed in your own product are a planned roadmap item; the roadmap page says so. Publishing is docs/rest-api.md §19.
One Docker image beside a PostgreSQL metadata database and Redis. ./app.sh --start --demo nyc builds the image, downloads the published sample data and brings the stack up; the reference compose file and the demo quickstart are docs/deployment.md Appendix A and Appendix B.
Every statement carries a query timeout and the run an overall deadline; a node that outlives its budget fails with pipeline.node.query_timeout and the elapsed time — and the SQL probe returns the plan — so the agent fixes the shape instead of guessing. The limits and cancellation are docs/dag-executor.md §5.
Not today. It sits beside them: it can feed Tableau a governed dataset through an API or as Parquet on your own bucket, and it replaces the warehouse-plus-dbt step for teams that never wanted one. Dashboards, scheduling and alerts are on the roadmap page, undated. The comparison pages cite docs/versioning.md §3 for what is versioned.
For the engineer who has to run it
The page above promised the outcome. From here down is the mechanism: what a dataset is here, where it runs, what the agent may and may not do, and what a release costs you. Every card below cites the spec section it comes from — view source and spot-check us.
A dataset here is a pipeline: a versioned JSON DAG of SQL nodes the agent authors over MCP against your real schemas. Each node runs on its own engine — Postgres, MySQL, SQL Server, Oracle, SQLite, DuckDB, Parquet and Iceberg on S3 through dp-lake — and cross-engine joins happen in an in-memory staging database that exists only for the run. Nothing is copied to a warehouse; nothing lands. A release is immutable, and a released pipeline can be published as a GET endpoint.
One server, eight engines
Postgres, Oracle, SQL Server, MySQL, H2, DuckDB, SQLite and dp-lake, through one endpoint and one set of scoped keys. Each page has that engine's driver facts and a copy-paste client config.
Every dialect has its page — H2 (also the staging engine) and dp-lake (LAKE: Parquet and Iceberg on S3 read in place) included. The dp-lake product page →
The agent loop
Agents are first-class principals here — not a plugin bolted onto a human UI. Your agent gets its own scoped credentials, its own protocol surface, and errors it can parse without scraping stack traces.
Mint it on the API section — /api-console, where the endpoints it may call and the MCP connection already live — or with POST /api/v1/auth/api-keys.
Keys are shown once, stored Argon2id-hashed, pinned to one workspace, given an expiry, and carry hierarchical scopes (read, execute, author — admin is for people, never issued to a key). An agent's key and a program's key are the same kind of credential.
/mcpThe MCP endpoint authenticates API keys only — DP-API-Key header or Bearer dpk_…. No cookies, no session roulette.
Exactly 41 tools cover the full lifecycle: introspect datasources, author templates and pipelines, publish endpoints, register lake tables, execute, inspect.
The agent creates a SQL template and a pipeline as declarative JSON — nodes, dependencies, parameters. Diffs review like code, because they are.
Execution streams per-milestone events over SSE, with heartbeats, so the agent knows where things stand.
executions_get_result returns the actual rows. The agent verifies its own work instead of asserting success.
This isn't a toy loop: the published demo set is ~4.9M sampled NYC taxi rows across Postgres, MySQL and SQLite, US trade at HS-6 grain on DuckDB, and 471,851,707 rideshare trips as Parquet on S3 that nothing downloads. An agent authors and runs against exactly that.
Shipped, not promised
Each card cites the spec section it comes from — view source and spot-check us. Caveats included where they exist — honesty is the brand.
Pipelines are declarative JSON — schema_version, nodes, depends_on, parameters, settings. Every update mints a new immutable integer version, and the contract is additive-only.
A save-time scan walks the parsed template AST against a forbidden-construct list — ?eval, ?new, ?api, in-body imports — so SSTI vectors are rejected before a template is ever saved.
Declared parameters bind as :name on a prepared statement — never interpolated. Interpolating one into the SQL text instead is refused at save with template.validation.parameter_interpolated.
Every template version is immutable — body, imports, dialect and engine are frozen once stored, versions are never reused or renumbered, and each carries a content hash. Pipelines pin {name, version}, so a new template version is invisible to them until an author moves the pin.
Templates are typed: sql (dialect required) or html (dialect absent, auto-escaping on). The type is fixed at creation and a database constraint enforces the pair.
A template's or pipeline's name is its path — nyc/mobility/revenue_by_borough, 2 to 10 segments, and the first one is a folder: the root holds folders only. A flat name is refused with details.reason = "folder_required".
An agent that invents a NEW top-level root is refused until it says so on purpose: confirm_new_root, with the roots that already exist in the error's details. Asking a person before minting a namespace is a guarantee here, not advice.
One draft per entity, at most. Releasing locks the version: we never modify a released pipeline — the next change copies it into a new draft. Discard drops the draft and returns the number to the pool.
The editor's version select is read-only for anything but the working version, and Edit reuses the existing draft or copies the selected version into a new one — the rule is visible on screen, not just enforced behind it.
The reverse arrow, answered as two different questions. Who uses this version now? scans every pipeline's working version and names the pipeline, the node id and the version carrying the pin — a fact you can go and change, not a count.
Is it safe to delete? scans every version ever, because historical versions stay executable. Delete is refused with 409 template.in_use while any pipeline version pins it.
Postgres, Oracle, SQL Server, MySQL, H2, DuckDB, SQLite and dp-lake — dp-lake is object storage read in place. One pipeline contract over all of them.
Oracle and MySQL drivers are opt-in for license reasons (-Poracle/-Pmysql, or drop the jar in lib/); a missing driver fails at save with datasource.driver_not_loaded. Everything else is in the published image, the DuckDB extensions included — a hardened deployment needs no egress to an extension repository.
A LAKE datasource queries Parquet and Apache Iceberg on S3 or any S3-compatible store with no warehouse and no load step. DuckDB is the engine; the catalog is the server's own registry, dp-catalog — tables are registered over REST or by an agent through the lake_tables_* tools.
Hive-partitioned Parquet is scanned with partition pruning, so a date predicate reads the days it names and not the table. It is a read connector: there is no write path to the bucket. The whole dp-lake story →
Read the spec →Each execution gets its own isolated in-memory H2 tempdb — created at start, destroyed at end. Nodes stage ResultSets there, so downstream nodes join across engines.
A PIPELINE node executes a version-pinned child pipeline as a linked child execution — in-process, with lineage and family cancellation. The child keeps its own execution record, tempdb, stats and stream.
The DAG renders on a full-bleed canvas as node cards, with the facts inside the box: name, a type badge, datasource · dialect, template @ version, and after a run, elapsed time and rows.
One dock, four tabs — Details, Results, Errors, Events: a failure opens Errors, every event in arrival order.
It executes, streams, cancels and pages results. What it deliberately does not do is author: no edit mode in v1 — authoring is agent-first.
Read the spec →A failed node carries the catalogued code, the message, the node's own facts (datasource, dialect, template @ version), the rendered SQL in :name form — never a bound value — and the original exception chain with frames, root cause last.
The same record, built once, reaches the node_failed SSE event, the stored error_json, the editor's error surface and the agent's executions_get. No surface strips what another shows.
POST /api/v1/pipelines/{id}/execute returns text/event-stream with per-milestone events and heartbeats. No stream resumption — a dropped connection cancels the execution rather than orphaning it.
Cancel reaches the executing instance through Redis from any instance, and a per-node latch refuses to enter the driver once it has fired. A cancelled execution cannot come back SUCCESS — the window where it could was closed and is covered by a test that injects the descheduling rather than racing it.
Push dev → staging → prod from the UI. The screen lists only released versions newer than the target's; drafts never leave the authoring environment, and templates and child pipelines travel first, as a dependency closure.
The credential is a server-kind API key, not a principal — no user account, so rotation and offboarding are independent of any person. Rotation is: mint a second, set it on the sender, revoke the first — no restart on either side.
Instances are symmetric and stateless for API, MCP, UI and auth — no sticky sessions. Only a running execution is pinned to one JVM; cancel works from any instance through Redis, and results and history are readable from all of them.
Datasource pool invalidation rides a Redis channel: repoint or rotate a credential on one instance and every other evicts its pool and rebuilds from the row.
A crashed instance's in-flight executions are swept to ABORTED by an idempotent stale-execution sweep every replica runs.
Everything a program uses to talk to a workspace, on one screen: the endpoints you have published as GET /api/x/…, the keys that may call them, and the MCP connection block — the tool count rendered live from the catalog, never a literal.
It is honest about what it cannot show: no "calls in the last 24 h" column — every serve is audited, but nothing tallies them per endpoint — so the card states the absence instead of filling it from a sample.
Read the spec →Eight HikariCP settings are on the datasource form, each prefilled with its effective default and labelled with where that default came from. A field left alone is not persisted, so a later change to a product default still reaches the datasource.
Out-of-range values are refused, not accepted — every floor restates a rewrite Hikari would otherwise perform silently, and the test that justifies them builds a real pool and watches it happen.
Read the spec →Editing or deleting a datasource retires its pool instead of closing it: new leases build a fresh pool from the new row at once, idle connections close now, and a connection still in use closes when its borrower returns it — never mid-statement.
Delete asks the question first: the dialog runs the any-version usage scan before it offers anything, and lists pipeline › node (v3 released) for every reference. The 409 and the dialog cannot disagree — same scan.
Logins, key lifecycle, scope denials, user admin, workspace events, promotion accept/reject, every MCP tool call and each credential-decryption point — recorded with actor, cause and source IP.
Failures arrive as stable {domain}.{entity}.{failure} codes in a uniform error envelope — a catalog enforced against the code by drift tests, and the same codes over MCP. Agents get errors they can branch on, not prose to parse.
you › Which rideshare company carried the most trips in each borough last quarter?
| borough | top company | trips | borough trips | share |
|---|---|---|---|---|
| Manhattan | Uber | 17,660,839 | 23,669,163 | 74.62% |
| Brooklyn | Uber | 11,946,362 | 16,057,699 | 74.40% |
| Queens | Uber | 9,786,602 | 12,847,231 | 76.18% |
| Bronx | Uber | 6,078,750 | 7,572,899 | 80.27% |
| Staten Island | Uber | 715,593 | 918,360 | 77.92% |
GET /api/x/demo/top-company-by-borough?anchor_date=2025-01-01 · after release · key scoped to /demo
from the demo workspace — a real result, not a promise
| endpoint | serves | key reach |
|---|---|---|
GET /api/x/demo/top-company-by-borough?anchor_date=2025-01-01 |
demo/top_company_by_borough · v1 | /demo/** |
from the demo workspace — released by a human, served as this version forever
Every request resolves exactly one workspace; all authored content is workspace-scoped; API keys are pinned to one workspace; membership roles are additive flags — viewer, author, promoter, workspace admin and super admin.
The management UI ships: create, switch, manage members, a navbar switcher and add-by-email.
Three kinds. A user key is an agent's key and a program's key — its authority is its scopes. An endpoint key authorises the published paths it is bound to. A server key opens the promotion routes and nothing else.
The confinement is decided centrally, before any handler's annotation is read — a new route cannot become quietly reachable to a scopeless key.
Read the spec →There is deliberately no tool that registers a datasource, and none that mints a key: a credential through a tool call transits the agent's context and transcript. A person adds the datasource in the UI; the agent uses it by name.
What the agent keeps is everything that needs no secret: list, get, test, schemas, tables, columns, preview rows — enough to author without a password.
Read the spec →Humans sign in through OIDC SSO. Agents get their own API keys with hierarchical scopes — separate principals, separate blast radii.
No identity provider? Turn on local accounts and the whole product runs with zero external setup. Hashes are Argon2id through the same hasher API keys use; there is no self-registration — an admin creates the account, or config seeds the first admin only.
Every seeded or admin-reset credential is one-time and the app refuses every other screen until it is changed. Five consecutive failures lock the account — and the unknown-email path spends the same hashing memory, so timing cannot enumerate.
Read the spec →is_readonly forbids write-shaped uses — enforced at save time, at an executor backstop, and as Hikari readOnly. Admin-gated on globals. The flag is a contract, not containment: pair it with a SELECT-only DB user.
Passwords are AES-256-GCM encrypted with the row's name bound as additional authenticated data, so a ciphertext moved between rows fails to decrypt. The app refuses to start without a valid key.
Every credential carries a key version and datapipelines.db.key-provider selects where the key comes from — one contract test every provider passes.
Charts and dashboards are next — see the roadmap.
Your infra, your rules
Runs on your hardware, under your policies. The defaults are paranoid so you don't have to be.
DATAPIPELINES_ENV) and how careful the product should be (DATAPIPELINES_POSTURE: development or hardened). Configuration lives in exactly two files — a tracked deploy/env/defaults.env carrying every non-secret default, and a git-ignored deploy/secrets.env that ./app.sh --scaffold writes for you with every secret generated. The hardened posture refuses the demo at boot.
429 rate_limit.unavailable rather than waved through. A limiter that opens under its own outage is not a limit.
Explore the sample data first. Connect your own databases when you’re ready.