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.
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
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
localhostand cloud metadata names, then rejects every address the hostname resolves to.
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.
Migration operations
Two operations exist only for connections that predate product scoping:
GET /v1/management/connections/unassignedlists connections with no product.POST /v1/management/connections/{id}/assignattaches one to the product in theX-Product-Nameheader.
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.

