> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lerian.studio/llms.txt
> Use this file to discover all available pages before exploring further.

# Connections

> Register, test, update, and delete a Fetcher connection — the named, credential-encrypted reference to one external database that every job addresses.

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

***

| Field                     | Notes                                                                                                                                                                     |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `configName`              | The identity a job uses. 3 to 100 characters, letters, digits, underscores, and hyphens only.                                                                             |
| `type`                    | One of `POSTGRESQL`, `MYSQL`, `ORACLE`, `SQL_SERVER`, `MONGODB`, upper-case. Request validation matches those five strings exactly, so any other casing fails with `400`. |
| `host` and `port`         | The port must fall between 1 and 65535.                                                                                                                                   |
| `databaseName`            | Required.                                                                                                                                                                 |
| `schema`                  | Optional. Narrows discovery and extraction to one namespace.                                                                                                              |
| `userName` and `password` | Both required. The Manager encrypts the password under its credential key and records the key version with the stored record.                                             |
| `ssl`                     | Optional block. When you set it, both a mode and a CA become mandatory.                                                                                                   |
| `metadata`                | Free-form key-value data. You can filter the connection list by it.                                                                                                       |

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

***

<Steps>
  <Step title="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`.
  </Step>

  <Step title="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.
  </Step>

  <Step title="Discover">
    `GET /v1/management/connections/{id}/schema` returns the tables and fields that Fetcher finds on the live datasource. See [Schema discovery](/en/fetcher/fetcher-schema-discovery).
  </Step>

  <Step title="Use">
    Reference the connection by its `configName` in the `mappedFields` map of an extraction job.
  </Step>

  <Step title="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.
  </Step>
</Steps>

## 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](/en/fetcher/fetcher-configuration).

<Warning>
  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.
</Warning>

## 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

***

<Warning>
  **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.
</Warning>

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`.

<Note>
  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.
</Note>

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

***

<CardGroup cols={2}>
  <Card title="Schema discovery" icon="table-list" href="/en/fetcher/fetcher-schema-discovery">
    Read a datasource schema, cache it, and validate a job against it.
  </Card>

  <Card title="Architecture" icon="sitemap" href="/en/fetcher/fetcher-architecture">
    The Manager, the Worker, and the Engine they both run over.
  </Card>
</CardGroup>
