---
title: "Take the redirect back and finish the connection"
method: POST
path: "/api/v1/banking/ponto/callback"
tags: ["Money"]
---

# Take the redirect back and finish the connection

`POST /api/v1/banking/ponto/callback`

## Request

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

Exchanges the code for tokens, learns which Ponto organization they are for, and lists the accounts the owner shared. The tokens are sealed server-side and never leave it. A state is spent once, only by the person who started it, and only within two hours, long enough for a first sign-up at Ponto; one that comes back later answers 400 with `details.late`, and connecting again goes straight to Ponto’s sign-in. A second copy of a redirect that already finished answers 409 naming the connection it made, and one that lands while the first is still exchanging answers 409 with `details.finishing`. The work after the claim runs to the end even when the caller has gone. A newer grant for the same organization retires the older one, whose accounts move across; one on another team is ended there. A grant that shares nothing is ended at Ponto rather than kept, and the older grants it replaced stop with it. One whose accounts cannot be listed is kept, and answers 400 with `details.listed` false and `details.connectionId`, to be picked up with `/ponto/connections/{id}/accounts`. A shared account whose IBAN the portfolio already holds is linked on the spot, also where an older grant read it under another Ponto id, and the rest wait for `/ponto/link`; an account of the worked example is never paired. While bank accounts are charged, a match links on its own only for an owner, and only while it stays within the accounts the plan includes or is the account the connection was started from, at the price agreed when it was started; every other match comes back suggested and unlinked, and every shared account left unpaired is noted on the connection. An account an older grant read that this one does not share stops here and is named in `notShared`. An account whose IBAN another connection on this team already reads, or that another of the caller’s teams holds, is marked and never paired, and someone no longer an owner keeps the grant and links nothing.

## Request body

- object
  - `state` string, required
  - `code` string, required

## Response `200`

Connected, and here is what it reaches.

- object
  - `ok` unknown, required
  - `data` object, required
    - `connectionId` string, required: Stable record id, e.g. `lse_012`.
    - `teamId` string, required: The team the connection belongs to.
    - `organizationName` string: The Ponto organization the owner consented for.
    - `accounts` object[], required: What the owner shared. Each is linked with `/ponto/link`.
      - `id` string, required: Ponto’s own id for the account. Hand it back to `/ponto/link`.
      - `iban` string
      - `currency` string
      - `description` string
      - `institution` string: The bank’s name, as Ponto knows it.
      - `institutionId` string
      - `balance` number: The balance Ponto last saw, in cents.
      - `authorizationExpiresAt` string: When the bank’s consent for this account is expected to run out.
      - `suggestedBankAccountId` string: The portfolio account with the same IBAN, where it may be paired.
      - `linkedBankAccountId` string: The portfolio account this already feeds.
      - `readElsewhere` boolean: Another connection on this team already reads the same IBAN, so this one is left at Ponto and cannot be linked.
      - `heldElsewhere` boolean: Another team the caller is on holds this IBAN and this team does not, so it is never paired here.
    - `startedFrom` string: The portfolio account the connection was started from.
    - `stoppedElsewhere` number: Connections on other teams this grant replaced. Ponto feeds one team at a time, so they stopped there.
    - `stopped` number: Accounts this team was reading that stopped, because the new grant shared none.
    - `notShared` string[]: Portfolio accounts an older grant read that this one did not share, so they stopped here.
    - `stoppedOn` object[]: The teams among `stoppedElsewhere` the caller is on, named because they can open them. Anyone else is told only how many.
      - `teamId` string, required: Stable record id, e.g. `lse_012`.
      - `name` string, required
    - `ownerMustConnect` boolean: The caller is no longer an owner of the team, so the grant is kept and nothing was linked.
  - `meta` object, required
    - `requestId` string, required: Echoed in the x-request-id header. Quote it in a bug report.

## Other responses

- `400`: A state that matches nothing or came back too late, a connection Ponto did not finish, or accounts that could not be listed. Once the state is spent, `details.connectionId` names the attempt, so it can be tried again from a browser that did not start it.
- `403`: The connection is on a team the caller is not on, or somebody else started it. Or, while bank accounts are charged, the caller is an assistant rather than a person on screen (`details.reason` `on_screen`), and the state is left unspent.
- `409`: Another copy of this redirect already finished the connection, and `details.connectionId` names it, to be picked up with `/ponto/connections/{id}/accounts`; with `details.finishing` it is still being finished, so ask again in a moment.

---

[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)
