Skip to main content
A connection is a stored, named reference to one external database. It carries the datasource type, the host and port, the database name, the credentials, and any TLS settings. Every extraction job and every schema call addresses a datasource by the connection’s configName, never by its host. Fetcher owns the credential from the moment it arrives. It encrypts the password before storage and never returns it.

What a connection holds


The Manager rejects an invalid TLS mode for the declared type. Each database driver accepts a different set of modes, and Fetcher validates the mode against the type before it stores the record.

Lifecycle


1

Create

POST /v1/management/connections with the connection body and an X-Product-Name header. The header names the product that owns the connection. A successful create answers 201 Created.
2

Test

POST /v1/management/connections/{id}/test opens a real connection to the datasource and reports the round-trip latency. Run it before any job depends on the connection.
3

Discover

GET /v1/management/connections/{id}/schema returns the tables and fields that Fetcher finds on the live datasource. See Schema discovery.
4

Use

Reference the connection by its configName in the mappedFields map of an extraction job.
5

Update or delete

PATCH applies a partial update and leaves omitted fields untouched. DELETE is a soft delete: the record keeps a deletion timestamp. Both operations answer 409 Conflict while jobs still run against the connection.

Encrypted credentials


Fetcher derives four independent keys from the single APP_ENC_KEY master key, through HKDF-SHA256. One of those keys protects datasource credentials. The password reaches storage encrypted with AES-256-GCM, and the stored record keeps the APP_ENC_KEY_VERSION that protected it. That version is what makes key rotation tractable: a record states which key opens it. The empty key version carries meaning. It marks an internal datasource — one an operator declares through DATASOURCE_{NAME}_* environment variables rather than through the API. Fetcher builds those connections in memory at startup and keeps no record of them at rest. The operator’s own secret manager owns the credential. See Configuration.
Both services must run with the same APP_ENC_KEY. The Worker needs it to open the credentials the Manager stored and to verify the signature on the message that carried the job. Neither service starts without a valid key of at least 32 bytes.

Testing a connection


The test operation does real work. It builds the connector, opens the datasource, runs the driver’s own connectivity check, and closes the connector on every path — success or failure. The response carries latencyMs, the observed round trip in milliseconds. Use it as a signal about the network path between Fetcher and the datasource, not as a benchmark of the database. The endpoint is rate-limited to 10 tests per minute per connection. A caller past that budget receives 429 Too Many Requests with a wait hint. The limit lives in the Manager, not in the Engine, so an embedded host sets its own policy. A failed test tells you that the connection failed. It does not tell you why in driver terms. Fetcher discards the underlying error, because that text can carry a DSN or a credential.

The 409 on active jobs


Fetcher blocks update and delete while jobs run against the connection. PATCH /v1/management/connections/{id} and DELETE /v1/management/connections/{id} both answer 409 Conflict when at least one job still runs against that connection’s configName.A caller must handle this. Treat it as “not yet”, not as “invalid”. Wait for the jobs to reach a terminal state and retry, or cancel them first.
The rule exists to keep a running extraction consistent with the connection it planned against. A host swap or a credential change mid-extraction would leave a job reading from a datasource nobody asked for. The Engine enforces the gate through an optional port, not through a hard dependency on job storage. The Manager answers the question from its job repository. An embedded host answers it however it tracks work — an in-memory set, a distributed lock, or a flat “no”. A host that supplies nothing gets no gate, and mutations proceed.

Host safety in multi-tenant mode


With MULTI_TENANT_ENABLED=true, Fetcher validates the host of every tenant-supplied connection before it dials. Validation runs at two layers:
  • At request parsing, a check without DNS rejects a blocked IP literal outright.
  • In the datasource factory, a resolving check rejects blocked hostnames such as localhost and cloud metadata names, then rejects every address the hostname resolves to.
Private ranges, loopback, and cloud metadata endpoints are blocked. A rejected host returns 400.
A DNS resolution failure is deliberately not a block. Turning “does not resolve” into a rejection would build a reconnaissance oracle and would fail legitimate connections during a transient DNS problem. The driver surfaces its own connect error instead.
The guard never applies to internal datasources. An operator who configures a datasource through environment variables has already made that decision.

Migration operations


Two operations exist only for connections that predate product scoping:
  • GET /v1/management/connections/unassigned lists connections with no product.
  • POST /v1/management/connections/{id}/assign attaches one to the product in the X-Product-Name header.
The assignment is one-time and irreversible. A second attempt on an already-assigned connection returns a conflict.

Next steps


Schema discovery

Read a datasource schema, cache it, and validate a job against it.

Architecture

The Manager, the Worker, and the Engine they both run over.