S
supero.docs
Documentation/Connectors/Database Connectors

Database Connectors

Bring your own PostgreSQL, Supabase, MySQL, SQL Server, Oracle, or MongoDB database into Supero — sync data into a managed schema, or read it live without copying a single row.

Overview

The database connector lets you point Supero at an existing SQL or MongoDB database and turn its tables into Supero schemas — either by syncing rows into the platform on a schedule, or by reading straight from the source in real time with no local copy. It runs as a connector: you configure a source (the database), a target (which tables map to which schemas), and a trigger (when it runs), and Supero handles discovery, type inference, and ongoing syncs.
  • •PostgreSQL (including Supabase), MySQL, SQL Server (MSSQL), Oracle, and MongoDB source databases
  • •Schema discovery: tables, views, columns, and primary keys are introspected automatically
  • •AI-assisted schema inference from sampled rows, reviewed before you create anything
  • •Sync mode (full CRUD copy) or live-read / live-read-write mode (no local copy)
  • •Incremental sync via a cursor column, with configurable lookback for non-unique timestamps
  • •Password auth, with cloud IAM and Oracle wallet options handled by the sync worker

ℹ️ Where this fits

Database connectors are for BYO relational and document databases you already run. For an analytical warehouse, see Warehouse Connectors. For a third-party HTTP API or SaaS service, use the managed services catalog or the Plugin Registry instead — see API Integrations. For the shared lifecycle, the console walkthrough, and the full endpoint list, see Connectors Overview.

Supported Engines

db_type accepts twelve values. Six are operational databases and are covered on this page: postgresql, supabase, mysql, mssql, oracle, and mongodb. The other six — snowflake, databricks, clickhouse, bigquery, redshift, and fabric — are analytical sources covered on Warehouse Connectors. If you do not supply a port, Supero fills in the engine’s standard default.
db_typeDefault PortNotes
postgresql5432Native async connection. Default schema "public".
supabase5432PostgreSQL wire protocol with a Supabase-specific connection profile — see the Supabase section below.
mysql3306Native async connection.
mssql1433ODBC Driver 18 for SQL Server. Default schema "dbo".
oracle1521 (1522 for Autonomous DB over TLS)Thin-mode client, no Oracle Client install needed. Sync only.
mongodb27017Native async driver, with table_names referring to collections. Sync only.
The connector validates db_type against this fixed set — an unsupported value is rejected at configuration time with a clear error rather than failing later during sync. The sync worker additionally accepts four spellings as aliases of the canonical names: postgres and pg for postgresql, mariadb for mysql, and sqlserver for mssql. Prefer the canonical names, since only those are in the published enum.

ℹ️ Six of the twelve are not in the console picker today

oracle, mongodb, clickhouse, bigquery, redshift, and fabric are not offered as options in the admin console’s database picker while they finish testing. They remain fully accepted over the API and over MCP, an existing connector on one of those types keeps working, and its own type stays visible on its detail page so editing a password cannot retype it as something else.

Core Configuration Fields

A database source is {plugin: "database", config: {...}}. Three payload shapes parse and all normalise onto source.config, but source.config is the one to send: it is the shape a saved connector round-trips through and the shape the API and MCP clients emit. Save-time repairs have historically gone inert on the other shapes.
FieldTypeNotes
db_typestringRequired. One of the twelve values above.
hoststringRequired for an OLTP source. Must resolve to a public, non-internal address — connections to loopback, link-local, private, or cloud-metadata ranges are blocked.
portintegerOptional — defaults to the engine’s standard port.
databasestringRequired. The database name (Oracle: often the service name).
schemastringOptional. Defaults to "public" for PostgreSQL and Supabase, "dbo" for SQL Server, and the uppercased username for Oracle.
usernamestringRequired.
passwordstringRequired for password auth; optional for MongoDB sources that do not require authentication.
query_typestring"table" (default) or "query".
table_namesarray of stringsRequired when query_type is "table". A list of table (or MongoDB collection) names — not a single table_name.
querystringRequired when query_type is "query". A single custom SELECT.
batch_sizeintegerRows fetched per batch. Default 1000. The config type allows 100 to 100000; the service clamps the effective value to between 1 and 10000.
connection_timeoutintegerSeconds. Default 30.
query_timeoutintegerSeconds. Default 3600.
ssl_modestringSee the TLS section below. Default "require".
incrementalobjectSee Incremental Sync below. Passed through to the plugin as-is.

⚠️ table_names, not table_name

Table selection is always a list under table_names, even for a single table (for example ["orders"]). A bare table_name string is not the current field name and is not read. A connector saved with query_type "table" and no table_names fails at connect time on every run — it saves cleanly, discovery binds the schemas, and then each run reads zero rows.

Custom query mode

json
{
  "db_type": "mysql",
  "host": "mysql.example.com",
  "port": 3306,
  "database": "app",
  "username": "reader",
  "password": "REDACTED",
  "query_type": "query",
  "query": "SELECT * FROM orders WHERE status = 'completed'",
  "batch_size": 5000
}

Table mode

json
{
  "db_type": "postgresql",
  "host": "db.example.com",
  "port": 5432,
  "database": "analytics",
  "schema": "public",
  "username": "reader",
  "password": "REDACTED",
  "query_type": "table",
  "table_names": ["customers", "orders"],
  "ssl_mode": "require"
}

Creating a Connector

A connector combines a source (the database config above), a target (which schemas the tables map to), and a trigger (how it runs). Create one over the API:
bash
curl -X POST https://api.supero.dev/api/v1/connectors \
  -H "X-API-Key: ak_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "billing-postgres",
    "display_name": "Billing Postgres",
    "project_uuid": "PROJECT_UUID",
    "source": {
      "plugin": "database",
      "config": {
        "db_type": "postgresql",
        "host": "db.example.com",
        "port": 5432,
        "database": "billing",
        "schema": "public",
        "username": "reader",
        "password": "REDACTED",
        "query_type": "table",
        "table_names": ["customers", "invoices"],
        "ssl_mode": "require"
      }
    },
    "target": {
      "namespace": "billing",
      "tables": [
        { "source": "customers", "schema": "Customer", "operation": "upsert", "mode": "sync" },
        { "source": "invoices",  "schema": "Invoice",  "operation": "upsert", "mode": "sync" }
      ]
    },
    "trigger": {
      "type": "schedule",
      "cron": "0 * * * *",
      "timezone": "UTC",
      "allow_manual": true
    }
  }'
Each entry in target.tables maps one source table to one target schema: source is the table or collection name (or "_default" for a single-stream source), schema is the Supero schema name, and operation is insert, update, or upsert (the default). Set enabled: false on a table entry to pause just that table without removing it from the connector.

ℹ️ The create response comes back with null source, target, and trigger

A successful create returns the connector with source, target, and trigger as null. That is the response shape, not a sign that your configuration was dropped. GET the connector back before you rely on anything having been stored — and always do that when you send a field that is not in the published config type.

Managing the connector

Method & PathPurpose
POST /api/v1/connectorsCreate a connector
GET /api/v1/connectorsList connectors (add ?include_stats=true for run stats)
GET /api/v1/connectors/{id}Get one connector
PUT /api/v1/connectors/{id}Full update — the only update method. PATCH is not routed and returns 405.
DELETE /api/v1/connectors/{id}Delete a connector — see the warning below
POST /api/v1/connectors/{id}/enableEnable
POST /api/v1/connectors/{id}/disableDisable
POST /api/v1/connectors/{id}/runTrigger a run. Body { "full_sync": true } ignores the incremental watermark. Needs the connector enabled and trigger.allow_manual not false.
POST /api/v1/connectors/{id}/runs/{execution_id}/cancelBest-effort cancel of a running execution
GET /api/v1/connectors/{id}/statusStatus plus recent execution history
POST /api/v1/connectors/{id}/testRe-test a saved connector using its stored credentials

🚨 Deleting a connector also deletes its object types

Deleting a connector also removes the object types it created, and the data in them. Export anything you need first.
You can test a source configuration before creating anything with POST /api/v1/connectors/test-config, sending the same source object you would use on create. It validates the plugin, required fields, connectivity, and auth, returns a per-check breakdown, and — for every SQL engine here — returns the list of tables and views on the source with a row estimate for each, under sample_record.tables. There is no separate list-tables endpoint; test-config and POST /{id}/test are the only synchronous table listers.

⚠️ A config you read back cannot be re-tested

Credentials are masked on every read, so a source object you fetched with a GET cannot be replayed into test-config — the masked password fails authentication even though the stored one is valid. To re-test a saved connector, either pass its id as connector_id in the test-config body, or call POST /api/v1/connectors/{id}/test, which reads the stored configuration server-side. The status indicator in the connector list can disagree with the connector’s own detail page for the same reason: trust the detail page for connectivity, and a stamped read for correctness.

Authentication

The default and fully supported path is plain username and password over TLS. The sync worker additionally understands cloud IAM auth and Oracle wallet material, selected with auth_type.

ℹ️ These fields are not part of the published database config type

auth_type and the Oracle wallet fields are honoured by the sync worker but are not attributes of the connector’s database config type, so they are not offered by the console form and may not survive config validation on the API path. Send them if you need them, then GET the connector back and confirm the field is still there before relying on it. Note also that live reads never use these paths — a live table always connects with username and password.
auth_typeBehavior
passwordDefault. Username and password from config.
iam_rdsAWS RDS IAM auth token — generates a short-lived token in place of a static password. Requires IAM auth enabled on the RDS instance. aws_region defaults to us-east-1.
iam_gcpGCP Cloud SQL IAM auth — uses Application Default Credentials to mint an access token. Requires Cloud SQL IAM auth enabled on the instance.
iam_azureAzure AD auth token for Azure Database for PostgreSQL/MySQL. Requires Azure AD auth enabled on the server; optional azure_tenant_id.
With any iam_ auth type, password is not required — a fresh token is generated immediately before each connection attempt, so no long-lived credential is stored.

Oracle: wallet and TCPS

Oracle connections default to a plain TNS connect on port 1521, which is correct for on-premise Oracle. For Oracle Autonomous Database — any host ending in oraclecloud.com — the connector automatically switches to a TLS connection (TCPS) on port 1522, mirroring what Autonomous Database actually exposes.
FieldPurpose
service_name / sidOracle service name (Autonomous Database) or SID (on-prem) to build the connect string.
use_tcpsForce TLS (TCPS) explicitly; also inferred automatically for oraclecloud.com hosts.
wallet_b64Base64-encoded Oracle wallet ZIP for mutual TLS — Autonomous Database’s default posture. Supplying a wallet implies TLS.
wallet_passwordPassword to decrypt the wallet, if the wallet requires one.
tns_aliasAlias to resolve via the wallet’s tnsnames.ora instead of building a DSN from host, port, and service.

⚠️ Autonomous Database needs a wallet

An Autonomous Database instance configured for mutual TLS rejects a walletless TLS connect. If you see a connection failure with no useful detail on an oraclecloud.com host, the usual cause is that no wallet was supplied. Oracle is also the one engine here that has not been exercised end to end against a live instance — plan a proof-of-concept before committing to it.

Supabase

Supabase is a first-class db_type rather than "a PostgreSQL host you type by hand". It speaks the PostgreSQL wire protocol, so everything on this page applies, but the connection details that actually work are specific enough to be worth stating.
SettingUse
hostThe session pooler: aws-0-<region>.pooler.supabase.com
port5432. Do not use 6543 — the transaction pooler rejects the login.
usernamepostgres.<project_ref>
project_refYour Supabase project reference, set as its own config field
schemaDefaults to "public"

⚠️ Two Supabase hostnames do not work

Avoid db.<project_ref>.supabase.co — it resolves to IPv6 only and is unreachable from the connector. Avoid port 6543, the transaction pooler, which rejects the pooled login format. Use the session pooler on 5432.

⚠️ Row-level security does not apply

The connector logs in as a database user, so Supabase row-level security policies written for the anon or authenticated roles do not filter what Supero reads. Scope the database role itself, and use tenant_binding for per-tenant filtering — see Live Data, Read-Only & Security.
Supabase system schemas (auth, storage, vault, and similar) are excluded from discovery, so a discovery run on a Supabase project proposes only your own tables.

TLS (ssl_mode)

ssl_mode controls how the connector encrypts the connection to SQL sources. MongoDB uses the same modes to decide whether to enable TLS on the connection URI. The default is require.
ssl_modeBehavior
disablePlaintext connection. No encryption.
requireDefault. Encrypts the connection but does not verify the server certificate — needed for most hosted providers whose certificates do not chain to a standard CA bundle.
prefer / allowAttempts encryption first; falls back to plaintext only if the server genuinely refuses TLS.
verify-caEncrypts and verifies the server certificate against a CA (supply ssl_ca_cert), without checking the hostname.
verify-fullEncrypts, verifies the CA, and verifies the hostname matches the certificate. The strictest mode.
Common alternate spellings are accepted and normalised, so disabled, preferred, required, verify_ca, and verify_identity all resolve to the equivalent mode above. Supply ssl_ca_cert (a PEM-formatted CA certificate as a string) when using verify-ca or verify-full; without it, those modes fall back to the system default CA store.

💡 Managed Postgres/MySQL providers

Providers like Aurora, RDS, and most managed MySQL and Postgres services require TLS and reject plaintext connections outright. Use require (the default) unless you specifically need certificate verification.

Sync vs. Live Read

Each table in target.tables has its own mode, so a single connector can mix modes across tables. This is the most important choice for an OLTP source: it decides whether Supero holds a local copy of your data or reads your database directly on every request.
modeWhat happensWrites
sync (default)Rows are copied into a Supero-managed local schema on the configured trigger. Reads and writes hit the local copy.Full CRUD against the local copy
live_readNo local copy. Every read goes straight to the source database in real time.Blocked — 403 on every write
live_readwriteNo local copy. Reads are real-time from the source, and writes are passed through to the source.Passed through to the source database

⚠️ Live modes are not available on every engine

Live reads are available on PostgreSQL (including Supabase), MySQL, and SQL Server only. Oracle and MongoDB sources are sync-only: there is no live read path for either, so a live_read table on an Oracle or MongoDB connector will not serve reads. Live write-through is narrower still — it is implemented for PostgreSQL and MySQL, while a write to a SQL Server live table is refused with 501 write_not_supported. Of the three, only PostgreSQL write-through has been proven end to end.
Live tables also carry stream_class: "entity" (the default) for bounded-cardinality dimension tables like customers or devices, and "event" for append-mostly time-series data like readings or logs, which is addressed by dimension and time range rather than per-row identity. An event stream is read-only even under live_readwrite, and get-by-uuid on one returns 501 — query it instead.

⚠️ A live table needs a confirmed binding

live_read and live_readwrite tables only become readable after you run discovery and confirm the field mapping, the primary key, and the tenant binding for that stream. An unconfirmed live table fails closed rather than silently exposing unscoped rows. In the console, a table set to live with no binding yet shows an amber note on the Settings tab telling you to re-run discovery.

Incremental Sync

For the complete set, see Schema Reference.
For sync-mode tables, incremental sync makes each run pull only rows that changed since the last watermark, instead of a full table scan every time. Configure it under incremental on the source config.

🚨 mode is the switch, and nothing warns you if it is missing

A connector configured with enabled: true but no mode: "incremental" re-reads the whole table on every run, forever, while still describing itself as incremental. There is no error and no warning — the runs succeed, the row counts look plausible, and the only tell is that a run over a large table takes as long as the first one did. If you have an existing incremental connector, GET it and confirm the incremental block contains mode: "incremental".
json
{
  "db_type": "postgresql",
  "host": "db.example.com",
  "database": "app",
  "username": "reader",
  "password": "REDACTED",
  "query_type": "table",
  "table_names": ["orders"],
  "incremental": {
    "mode": "incremental",
    "cursor_column": "updated_at",
    "cursor_type": "datetime",
    "cursor_order": "asc",
    "lookback_seconds": 5
  }
}
FieldPurpose
modeRequired, and must be the exact string "incremental". This is the switch. Any other value — including omitting it while setting enabled: true — makes the run a full sync with no error and no warning.
enabledLegacy flag, retained for compatibility. It does not turn incremental on by itself — mode does.
cursor_columnRequired. The column to track — must be strictly increasing (or decreasing) for accuracy. On an http_api source the same field is named cursor_field.
cursor_typeHow to compare cursor values — "datetime" for a timestamp column, "integer" for a sequence, "string" for anything else. The worker also understands decimal, number, numeric, float, double, and real. An unrecognised spelling is not rejected: it is treated as opaque, meaning any change counts as an advance.
cursor_order"asc" (default) or "desc".
cursor_initial_valueOptional starting watermark for the very first run.
lookback_secondsOptional overlap window re-pulled before the stored watermark, so rows sharing the same second as the watermark are not dropped. A small default overlap is applied automatically for timestamp cursors when this is left unset; pass 0 explicitly to opt out.
The watermark is committed only when a whole run succeeds, and it never moves backwards. Changing the connector’s configuration resets it, and a run triggered with full_sync: true ignores it entirely and re-pulls everything. A reconcile_cron — a second, less frequent schedule that also ignores the watermark — is the supported way to repair drift on an ongoing basis; it can be set on the Settings tab of a connector’s detail page.

ℹ️ There is no watermark view in the console today

Neither the console nor the API exposes where an incremental cursor currently sits. Run History shows how many rows the last run wrote, not the position it reached. If you need to confirm a cursor has advanced, compare row counts across runs, or trigger a full re-sync when you want a known-good baseline.

💡 Pick a cursor column carefully

A cursor column that is not unique per timestamp (an updated_at at second precision on a busy table, for example) can still race with new writes at the exact watermark boundary. The lookback overlap combined with upsert-mode writes handles this safely; if your target table writes in strict append or insert-only mode, disable the overlap explicitly and choose a strictly unique, monotonic cursor instead.

Discovery

Discovery inspects the source database and proposes Supero schemas for you to review — it never writes application data itself. Trigger it independently of a sync, even while the connector is disabled:
bash
curl -X POST https://api.supero.dev/api/v1/connectors/CONNECTOR_ID/discover \
  -H "X-API-Key: ak_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{ "streams": ["orders"], "sample_limit": 100, "timeout_seconds": 300, "infer_from": "samples" }'
Discovery does three things: it lists tables and views (with type and an estimated row count where available), introspects each table’s columns — mapping SQL types to Supero types like integer, float, string, boolean, uuid, json, datetime, date, and array — and, best-effort, detects the primary key. Column and primary-key introspection never abort discovery on their own: a table that fails introspection is still listed, just without column hints.
Poll status, then fetch the inferred schemas once discovery completes:
Method & PathPurpose
GET /api/v1/connectors/{id}/discover/{execution_id}Poll discovery progress
GET /api/v1/connectors/{id}/discover/{execution_id}/resultsDiscovered streams, inferred schemas, field mappings, warnings, and (optionally) sample rows
The results endpoint returns inferred_schemas for you to review — nothing is created automatically. You explicitly choose which inferred schemas to create, then confirm field mappings for any tables you want in live mode. It returns HTTP 200 even when discovery failed, so read the error and message keys rather than the status code.

⚠️ Sampled discovery is capped at 100 tables per run

Naming more than 100 streams with infer_from "samples" is rejected with 400 too_many_tables_for_sampled_discovery. Either select fewer tables per run, or send infer_from: "schema", which skips row sampling and scales much further at the cost of less accurate type inference.

SQL Server and Oracle Notes

MSSQL and Oracle connect through their native client libraries: ODBC Driver 18 for SQL Server, and python-oracledb in thin mode for Oracle, which needs no separate Oracle Client install. A configuration test validates your host, credentials, and reachable database. Full connectivity — listing tables and views — is confirmed at the discovery step, which is also where you see the actual schema Supero proposes.
For SQL Server, the default schema is "dbo" — Supero coerces a stray "public" default to "dbo" automatically, since public is a PostgreSQL convention. For Oracle, the default schema is your uppercased username unless you set one explicitly, matching Oracle’s convention of one schema per user.

⚠️ SQL Server sync is less exercised than its discovery path

SQL Server and MySQL both discover reliably against real remote servers, but sync runs on them have not yet landed rows in an end-to-end verification build. Live reads on both are verified. If you are planning a sync-mode SQL Server or MySQL connector for production, run a proof-of-concept and check the row counts in Run History before you depend on it.

MongoDB Notes

MongoDB is configured the same way as the SQL engines, with table_names referring to collection names. A few fields are MongoDB-specific:
FieldPurpose
auth_sourceAuthentication database. Defaults to "admin".
replica_setOptional replica set name for a replica-set deployment.
Field type inference is done from sampled document values rather than a fixed schema: booleans, integers, floats, strings, datetimes, dates, arrays, and nested objects (mapped to json) are detected automatically. Password is optional for MongoDB sources that do not require authentication.

⚠️ MongoDB is sync-only

There is no live read path for MongoDB. Every table on a MongoDB connector must be sync. MongoDB is also not offered in the console picker today — create it over the API or MCP.

Next Steps