---
title: "Point a connection at the account it feeds"
method: POST
path: "/api/v1/banking/ponto/link"
tags: ["Money"]
---

# Point a connection at the account it feeds

`POST /api/v1/banking/ponto/link`

## Request

- Base URL: `http://localhost:5180`
- URL: `http://localhost:5180/api/v1/banking/ponto/link`
- Auth: API key in cookie `better-auth.session_token`

Checks the account is one Ponto actually shares and is in the same currency, then turns the portfolio account’s own feed into a Ponto feed rather than adding a second one. Linking the same pair again changes nothing; re-pointing either side of an existing pair is refused until it is disconnected, so nothing is left reading, or billed, without the owner knowing. Where both sides carry an IBAN and they differ, the pair is refused with `details.ibanDiffers` unless `correctIban` writes the bank’s IBAN onto the portfolio account, with an audit line. An IBAN another connection on this team already reads, or that another of the caller’s teams holds, is refused. A connection a newer grant replaced links through that grant. An account of the worked example is never paired. While bank accounts are charged, a pair that adds an account takes an owner on screen and a plan in trial or active, and one past the accounts the plan includes also takes `agreed` at the team’s current price; a pair replacing one this team already reads adds nothing and asks for nothing. Owners only.

## Request body

- object
  - `connectionId` string, required: Stable record id, e.g. `lse_012`.
  - `bankAccountId` string, required: The account in the portfolio.
  - `externalAccountId` string, required: Ponto’s own id for the account, from `/ponto/callback`.
  - `correctIban` boolean: Where the two IBANs differ, true writes the bank’s IBAN onto the portfolio account, with an audit line, and links them. Without it such a pair is refused.
  - `agreed` object: The price a month of an account past the plan’s own that the owner saw and agreed to, copied from `billing.price` in GET /ponto/connections. Needed only where the request takes the team past the accounts its plan includes; without it, or at a price that is no longer the team’s, the answer is 409 with `details.reason` `price` and the current price.
    - `priceId` string, required: The Stripe price, as `billing.price.priceId` in GET /ponto/connections.
    - `unitAmount` integer, required: Minor units a month for one account past the plan’s own, as shown.
    - `currency` string, required: Lowercase, as shown.

## Response `200`

Linked, and the feed is live.

- object
  - `ok` unknown, required
  - `data` object, required
    - `connectionId` string, required: Stable record id, e.g. `lse_012`.
    - `bankFeedId` string, required: Stable record id, e.g. `lse_012`.
  - `meta` object, required
    - `requestId` string, required: Echoed in the x-request-id header. Quote it in a bug report.

## Other responses

- `400`: The connection is not live, Ponto does not share that account, the currencies or the IBANs differ, or the account belongs to the worked example.
- `403`: Only an owner of the team may do this, and support may not do it for a customer; a member is refused with `details.reason` `owner_only`. While bank accounts are charged, adding a charged account also takes a person on screen, so an assistant’s token is refused with `details.reason` `on_screen`.
- `404`: No such connection or bank account on this team.
- `409`: Either account is already paired with another, another connection on this team already reads that IBAN, another of the caller’s teams holds it, the team’s plan connects no more bank accounts, or another check is reading the bank right now. Or: Where the request adds an account: `details.reason` `needs_plan`, with `why` `no_plan` or `past_due` and the `price`, when the team has no plan in trial or active; where the feed is free, `why` `no_plan_unbilled` with no price, when a plan can be sold and the team has none; `price`, with the current `price`, when the account goes past the ones the plan includes and `agreed` is missing or no longer the team’s price. Whatever it adds, `unavailable` with `why` when the price cannot be read or charging is not set up on this deployment. Nothing is written in any of them.

---

[API](https://skmtc.dev/dardaris/apis/dardaris-api.md) · [All operations](https://skmtc.dev/dardaris/apis/dardaris-api/llms.txt) · [OpenAPI document](https://skmtc.dev/dardaris/apis/dardaris-api/revisions/4aff33835c73?raw)
