> ## 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, use the vendor's hosted consent flow to bind an account when needed, mint a webhook token bound to the connection, and the aggregator's webhooks then drive inbound pulls. This guide covers the full lifecycle.

<Note>Credentials (`clientId`/`secret`) are **inbound-only**: Matcher seals them before persistence and never returns them in a response, a log, or an error. Every response on this surface is secret-free by construction. The tenant always comes 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`: optional opaque vendor account reference (Pluggy `itemId`, Belvo link id) threaded onto the webhook pull. Omit it to create a connection awaiting the end customer's consent. Bind the returned vendor reference later with `PUT`.
* `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": "",
  "awaitingConsent": true
}
```

## 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, bound connection using its already-sealed credential, addressed by `configName`. The vendor comes from the stored connection. This call takes no credential and returns none. A connection awaiting consent is not testable until its vendor account reference is bound.

```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 '{ "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 or a stored vendor without a connectivity-test path (Belvo today) surfaces through the standard error response. No test runs. Use the list response's `testable` field before offering the action.</Note>

## Connect an awaiting-consent connection

***

For a connection created without `accountRef`, mint a short-lived consent token and open the vendor's own consent widget in the end customer's browser. The response carries the token once. Matcher never persists it and never logs it. When the widget returns the vendor item or link id, bind it with `PUT /v1/discovery/aggregator-connections/{id}` and an `accountRef` body. You do not need to supply the credential again.

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

For an already bound connection, the same endpoint starts re-consent and returns `reconsent: true` with the exact bound `accountRef`. Use that echoed value with the vendor widget rather than a locally cached reference. A deployment without a consent path for the vendor returns `422`.

## 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": "POSTGRESQL", "category": "database" },
    { "type": "STRIPE", "category": "rest" }
  ]
}
```

This list excludes aggregator-vendor types (Pluggy/Belvo). The aggregator-connections surface above provisions them.

## Mint a webhook token

***

Mint a webhook token bound to an existing aggregator connection. The response carries the raw token and its provider-facing webhook URL **once**. Matcher stores only the token's SHA-256 hash.

```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>",
  "webhook_url": "https://api.matcher.example.com/v1/discovery/webhooks/pluggy/<raw-token>",
  "vendor": "pluggy"
}
```

Configure the returned `webhook_url` 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)                                                                 |
| `422`  | Existing vendor cannot be connectivity-tested, or the deployment has no hosted-consent path for that vendor |
| `409`  | Connection with that config name already exists                                                             |
