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

# Aggregator connections

> Provision Open-Finance data-aggregator connections (Pluggy, Belvo), test them, browse connector types, and mint webhook tokens for inbound pulls.

Aggregator connections let Matcher pull transaction data from Open-Finance data aggregators (Pluggy, Belvo). You create a connection with a sealed credential, mint a webhook token bound to it, and the aggregator's webhooks then drive inbound pulls. This guide covers the full lifecycle.

<Note>Credentials (`clientId`/`secret`) are **inbound-only**: they are sealed before persistence and are never returned in a response, a log, or an error. Every response on this surface is secret-free by construction. The tenant is always resolved from the JWT, never from the request body.</Note>

## Create a connection

***

```bash theme={null}
curl -X POST "https://api.matcher.example.com/v1/discovery/aggregator-connections" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "vendor": "pluggy",
    "configName": "pluggy-main",
    "baseUrl": "https://api.pluggy.ai",
    "accountRef": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
    "clientId": "...",
    "secret": "..."
  }'
```

Field values:

* `vendor` — `pluggy` or `belvo`.
* `configName` — unique (tenant-scoped) connection identity; a duplicate is a `409`. **The webhook token-mint endpoint binds a token to this name.**
* `baseUrl` — vendor API base URL, stored as the connection host.
* `accountRef` — opaque vendor account reference (Pluggy `itemId`, Belvo link id) threaded onto the webhook pull.
* `clientId` / `secret` — aggregator API credential; sealed and never emitted.

A successful create returns `201` with the secret-free connection descriptor:

```json theme={null}
{
  "vendor": "pluggy",
  "configName": "pluggy-main",
  "baseUrl": "https://api.pluggy.ai",
  "accountRef": "a1b2c3d4-5678-90ab-cdef-1234567890ab"
}
```

## List, get, update, delete

***

```bash theme={null}
# List (cursor-paginated, secret-free)
curl -X GET "https://api.matcher.example.com/v1/discovery/aggregator-connections?limit=20" \
  -H "Authorization: Bearer $TOKEN"

# Get by opaque id
curl -X GET "https://api.matcher.example.com/v1/discovery/aggregator-connections/{id}" \
  -H "Authorization: Bearer $TOKEN"
```

### Update

Edit an existing connection by id so a mistyped `baseUrl` is not permanent. The **vendor is immutable**. The credential is optional: supply **both** `clientId` and `secret` to rotate the sealed credential, or omit **both** to leave the stored secret intact. Supplying exactly one is a `400`.

```bash theme={null}
curl -X PUT "https://api.matcher.example.com/v1/discovery/aggregator-connections/{id}" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "configName": "pluggy-main",
    "baseUrl": "https://api.pluggy.ai",
    "accountRef": "a1b2c3d4-5678-90ab-cdef-1234567890ab"
  }'
```

### Delete

```bash theme={null}
curl -X DELETE "https://api.matcher.example.com/v1/discovery/aggregator-connections/{id}" \
  -H "Authorization: Bearer $TOKEN"
```

Delete soft-deletes the connection (`204`), freeing its config name for reuse. A non-aggregator connection id returns `404` on any by-id operation — this surface never confirms the existence of a non-aggregator row.

## Test a connection

***

Run a live connectivity check for an existing connection using its already-sealed credential, addressed by `(vendor, configName)`. No credential is supplied or returned.

```bash theme={null}
curl -X POST "https://api.matcher.example.com/v1/discovery/aggregator-connections/test" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "vendor": "pluggy", "configName": "pluggy-main" }'
```

```json theme={null}
{ "vendor": "pluggy", "configName": "pluggy-main", "healthy": true }
```

<Note>A credentials-don't-work outcome is an **expected** test result surfaced as `"healthy": false` with a `200` — not an error. A missing connection is a `404`.</Note>

## Connector types

***

List the connector types the engine registry has actually registered for this deployment, each tagged with a backend-derived category (`database` or `rest`). The list reflects the live registry — only connectors registered at boot appear. It drives the connection form's type-select.

```bash theme={null}
curl -X GET "https://api.matcher.example.com/v1/discovery/connector-types" \
  -H "Authorization: Bearer $TOKEN"
```

```json theme={null}
{
  "types": [
    { "type": "postgres", "category": "database" },
    { "type": "rest-generic", "category": "rest" }
  ]
}
```

Aggregator-vendor types (Pluggy/Belvo) are excluded here — they are provisioned through the aggregator-connections surface above.

## Mint a webhook token

***

Mint a webhook token bound to an existing aggregator connection. The raw token and its provider-facing webhook URL are returned **once** — only the token's SHA-256 hash is stored.

```bash theme={null}
curl -X POST "https://api.matcher.example.com/v1/discovery/webhooks/tokens" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "vendor": "pluggy",
    "connection_config_name": "pluggy-main"
  }'
```

```json theme={null}
{
  "token": "<raw-token-shown-once>",
  "webhookUrl": "https://api.matcher.example.com/v1/discovery/webhooks/pluggy/<raw-token>",
  "vendor": "pluggy"
}
```

Configure the returned `webhookUrl` in the aggregator's dashboard. A missing target connection returns `404`.

## Response codes

***

| Status | Meaning                                                        |
| ------ | -------------------------------------------------------------- |
| `200`  | Get, list, test, or connector-types returned                   |
| `201`  | Connection created / token minted                              |
| `204`  | Connection soft-deleted                                        |
| `400`  | Invalid vendor, partial credential pair, or invalid pagination |
| `401`  | Tenant could not be resolved                                   |
| `404`  | Connection not found (or not an aggregator)                    |
| `409`  | Connection with that config name already exists                |
