Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Connect a database or API

A connector is how a workflow reaches anything outside the process. You create one through the admin API, and every workflow references it by name. This guide creates one, keeps its credential out of the database, bounds what it may do, and uses it from a task.

Before you start

You need a server on http://localhost:8080 and a reachable external system to point the connector at. The examples use a PostgreSQL database; Add your first connector ships one in Docker if you need it.

Create one

Post the definition. It is live at once. Connectors have no draft step and no activation, the registry reloads on every change, and an update replaces the stored config rather than versioning it:

curl -s -X POST http://localhost:8080/api/v1/admin/connectors \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "orders-db",
    "connector_type": "db",
    "config": {
      "type": "db",
      "connection_string": "env://ORDERS_DB_URL",
      "max_connections": 5,
      "allow_private_urls": true,
      "operations": { "delete": false, "raw_write": false }
    }
  }'

Never write a credential into one

Reference the server’s environment instead:

{ "connection_string": "env://ORDERS_DB_URL" }

The reference resolves from the server’s environment each time the connector loads, so the stored row holds a variable name. Three things follow:

  • The database never holds the credential, so a dump is not a leak.
  • The same JSON works in dev, QA and production; only the variable’s value differs.
  • The connector survives export and import. A literal credential exports as "******" and is refused on import, so it cannot be promoted at all.

vault://<api-path>#<field> reads HashiCorp Vault. ${VAR} and ${VAR:-default} shell-style substitution also works, which is what the shipped postgres-orders example uses.

If an API wants its credentials in the query string, do not put them in the connector url. Use query_params, which keeps the resolved value out of the URL, and therefore out of traces, logs and error messages.

Say what it may do

Every connector carries operation gates, all allowed by default. Turning one off makes the call a validation error regardless of what any workflow asks:

{ "operations": { "read": true, "insert": true, "update": true,
                  "delete": false, "upsert": true, "raw_write": false } }

Both delete and raw_write must be off to make a SQL connector delete-proof. Raw SQL cannot be classified per operation, so db_write is gated as a whole.

An HTTP connector gates by method instead, as an allow-list. Empty means every method; naming even one makes the list exhaustive:

{ "operations": { "methods": ["GET"] } }

This is the cheapest blast-radius control Orion offers. It sits below the logic, and it survives every workflow change.

Reach a private address on purpose

Connections to private ranges are refused unless the connector says otherwise:

{ "allow_private_urls": true }

Most databases and caches are private, so most connectors set this. The point is that reaching an internal address becomes a stated decision. The unstated case, a workflow-authored connector reaching a metadata endpoint, stays refused by default.

Use it from a workflow

Name the connector, pass request data through params, and declare the schema the task may touch:

{ "id": "record", "name": "Insert the order",
  "function": { "name": "data_write", "input": {
    "connector": "orders-db",
    "params": { "total": { "var": "data.req.total" } },
    "write": { "op": "insert", "target": "orders",
               "values": { "total": { "param": "total" } }, "returning": ["id"] },
    "schema": { "entities": { "orders": { "columns": { "id": { "type": "int", "writable": false },
                                                       "total": { "type": "float" } } } } },
    "output": "data.created"
  }}}

Two things are doing real work there. params is the only door request data comes through, and every resolved value becomes a bound parameter, so the dialect is injection-safe by construction. schema declares what the task may touch; undeclared entities and columns are rejected, so a task without one reaches nothing.

Choose between the two data APIs

data_query / data_writedb_read / db_write
You writeA backend-neutral envelopeRaw SQL
Runs againstSQL, MongoDB, ElasticsearchSQL only
Injection safetyBy construction; no query text existsParameterized, but the text is yours
Bounded byThe task’s schema, plus connector gatesConnector gates and the database user

Use the portable dialect by default. Reach for raw SQL only when the dialect cannot express the query, such as a window function or a recursive CTE. Know that you have given up the schema bound when you do.

Verify

Test the connector from the API:

curl -s -X POST http://localhost:8080/api/v1/admin/connectors/orders-db/test

For a db or cache connector this opens a real connection. For http it issues a real GET with real credentials, which is the point: a wrong bearer token is invisible until traffic hits it. A 401 or 403 is reported as not reachable. Before the server is running at all, orion-server test-connectivity probes the configured database and, when enabled, Kafka.

Keep it healthy

Each connector has its own circuit breaker and, for HTTP, its own retry policy. Turn breakers on globally with engine.circuit_breaker.enabled = true; they are off by default. Inspect and reset them at /api/v1/admin/connectors/circuit-breakers. See Timeouts, retries and circuit breakers.

Next steps

Last verified 14 September 2026