curl --request POST \
--url https://api-sandbox.ledgersyncappv2.com/v3/connections/{connection_id}/reauthorize \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{}'import requests
url = "https://api-sandbox.ledgersyncappv2.com/v3/connections/{connection_id}/reauthorize"
payload = {}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({})
};
fetch('https://api-sandbox.ledgersyncappv2.com/v3/connections/{connection_id}/reauthorize', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api-sandbox.ledgersyncappv2.com/v3/connections/{connection_id}/reauthorize",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api-sandbox.ledgersyncappv2.com/v3/connections/{connection_id}/reauthorize"
payload := strings.NewReader("{}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api-sandbox.ledgersyncappv2.com/v3/connections/{connection_id}/reauthorize")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api-sandbox.ledgersyncappv2.com/v3/connections/{connection_id}/reauthorize")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{}"
response = http.request(request)
puts response.read_body{
"connection_id": "con_FINICITY_41294",
"source": "FINICITY",
"action": {
"kind": "reauthorize",
"reauth_url": "<string>",
"expires_at": "2023-11-07T05:31:56Z"
}
}{
"error": {
"code": "unknown_api_key",
"message": "The API key you presented doesn't match any active key.",
"type": "auth_error",
"doc_url": "https://portal.ledgersyncappv2.com/errors/unknown_api_key",
"category": "AUTH_ERROR",
"is_user_actionable": true,
"source_diagnostic_code": "FIN-103",
"param": "client.email",
"trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
"errors": [
{
"param": "client.email",
"message": "must be a valid email address",
"code": "invalid_email"
}
]
}
}{
"error": {
"code": "unknown_api_key",
"message": "The API key you presented doesn't match any active key.",
"type": "auth_error",
"doc_url": "https://portal.ledgersyncappv2.com/errors/unknown_api_key",
"category": "AUTH_ERROR",
"is_user_actionable": true,
"source_diagnostic_code": "FIN-103",
"param": "client.email",
"trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
"errors": [
{
"param": "client.email",
"message": "must be a valid email address",
"code": "invalid_email"
}
]
}
}{
"error": {
"code": "unknown_api_key",
"message": "The API key you presented doesn't match any active key.",
"type": "auth_error",
"doc_url": "https://portal.ledgersyncappv2.com/errors/unknown_api_key",
"category": "AUTH_ERROR",
"is_user_actionable": true,
"source_diagnostic_code": "FIN-103",
"param": "client.email",
"trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
"errors": [
{
"param": "client.email",
"message": "must be a valid email address",
"code": "invalid_email"
}
]
}
}{
"error": {
"code": "unknown_api_key",
"message": "The API key you presented doesn't match any active key.",
"type": "auth_error",
"doc_url": "https://portal.ledgersyncappv2.com/errors/unknown_api_key",
"category": "AUTH_ERROR",
"is_user_actionable": true,
"source_diagnostic_code": "FIN-103",
"param": "client.email",
"trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
"errors": [
{
"param": "client.email",
"message": "must be a valid email address",
"code": "invalid_email"
}
]
}
}Reauthorize a connection
Mint a hosted page the end-user opens to repair credentials or
re-consent at the institution, typically when the connection
reports status = requires_action.
This endpoint is not gated on status. It accepts an active
connection, but Finicity Connect Fix may report that there is
nothing to repair. Calling it on a healthy connection does not
guarantee that an account picker will open.
Adding Finicity accounts
Use POST /v3/clients/{client_id}/connections with the existing
Client and the bank’s v3 catalog institution_id. When routed to
Finicity, that opens Full Connect on the existing provider customer.
Have the end-user choose the option to add accounts under the
existing bank, then complete authorization and account selection.
The same institution login reuses the existing LedgerSync
connection. Starting Full Connect does not force a new connection
on every session; a different provider login can create one.
See that endpoint for detecting newly added accounts.
A Finicity POST /connections/{connection_id}/refresh updates
accounts already held by LedgerSync. It does not open account
selection or import an account that the user has not shared.
What you get back
A hosted reauth_url, a LedgerSync-hosted page the end-user opens
to complete the source-specific flow (Finicity Connect Fix, MX
member reconnect, FDE credential recapture) against the existing
connection. Redirect the user to it; connection status changes
produce connection.active / connection.failed webhooks.
An already active connection may have no new status transition,
so do not require another connection.active event to finish a
no-op session.
The response also carries source: the provider the URL leads to.
See “Ids are not always stable” below.
The URL is a reusable bearer credential
The link is not single-use. It stays valid until
action.expires_at (currently about six days out) and anyone
holding it can open the end-user’s bank-linking session. Deliver it
over a channel you would use for a password reset, do not log it,
and do not embed it in a page that a third party can scrape.
Ids are not always stable
Repairs aim to retain existing con_ / acc_ / txn_ ids.
Finicity can issue a different underlying login during repair;
when that cannot be matched back to the existing connection, a
separate connection can result even if the response source
is unchanged. Re-list the Client’s connections and accounts after
the session instead of treating a matching source as proof of
stable ids.
For a bank LedgerSync now serves exclusively through a different provider, the URL we hand back is an add-bank link for that provider rather than a repair link for the connection you named. Today that is Finicity connections at such banks being redirected to MX, and American Express is on that list in production. The list is configuration and can change without an API version bump, so branch on the response rather than on a bank you hard-coded.
When the redirect happens, the end-user’s session creates a new
connection with a new con_ id and new acc_ / txn_ ids, and
the connection you passed is left as it was.
Detect it from the response: source differs from the source
segment of the connection_id you sent. On that branch the
connection.active webhook that follows carries the new
connection’s id, not the one you passed, so key off the Client
rather than the connection: wait for connection.active, then
re-list GET /v3/clients/{client_id}/connections and
GET /v3/accounts?client_id=... and re-map, rather than expecting
the old ids to come back.
Call it because a person asked, not on a timer
Each completed session can share accounts you were not syncing before, and newly shared accounts pull their statement history, which is billable. Trigger this from an explicit user action. Do not schedule it, do not retry it in a loop, and do not mint a second link while the first is still valid.
Available for FINICITY, MX, and FDE. Uploaded-statement
(PDF) data is read-only and has no connection to reauthorize
(400). On MX and FDE you do not need this endpoint to pick up
a newly opened account: a plain refresh discovers it. See
POST /connections/{connection_id}/refresh.
Ownership is enforced server-side: a connection_id that
doesn’t belong to the authenticated customer returns 404.
Empty-body POST
This endpoint takes no request body. Send an explicit
Content-Length: 0 header so the upstream HTTPS load balancer
doesn’t reject the request with 411 Length Required.
curl --request POST \
--url https://api-sandbox.ledgersyncappv2.com/v3/connections/{connection_id}/reauthorize \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{}'import requests
url = "https://api-sandbox.ledgersyncappv2.com/v3/connections/{connection_id}/reauthorize"
payload = {}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({})
};
fetch('https://api-sandbox.ledgersyncappv2.com/v3/connections/{connection_id}/reauthorize', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api-sandbox.ledgersyncappv2.com/v3/connections/{connection_id}/reauthorize",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api-sandbox.ledgersyncappv2.com/v3/connections/{connection_id}/reauthorize"
payload := strings.NewReader("{}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api-sandbox.ledgersyncappv2.com/v3/connections/{connection_id}/reauthorize")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api-sandbox.ledgersyncappv2.com/v3/connections/{connection_id}/reauthorize")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{}"
response = http.request(request)
puts response.read_body{
"connection_id": "con_FINICITY_41294",
"source": "FINICITY",
"action": {
"kind": "reauthorize",
"reauth_url": "<string>",
"expires_at": "2023-11-07T05:31:56Z"
}
}{
"error": {
"code": "unknown_api_key",
"message": "The API key you presented doesn't match any active key.",
"type": "auth_error",
"doc_url": "https://portal.ledgersyncappv2.com/errors/unknown_api_key",
"category": "AUTH_ERROR",
"is_user_actionable": true,
"source_diagnostic_code": "FIN-103",
"param": "client.email",
"trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
"errors": [
{
"param": "client.email",
"message": "must be a valid email address",
"code": "invalid_email"
}
]
}
}{
"error": {
"code": "unknown_api_key",
"message": "The API key you presented doesn't match any active key.",
"type": "auth_error",
"doc_url": "https://portal.ledgersyncappv2.com/errors/unknown_api_key",
"category": "AUTH_ERROR",
"is_user_actionable": true,
"source_diagnostic_code": "FIN-103",
"param": "client.email",
"trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
"errors": [
{
"param": "client.email",
"message": "must be a valid email address",
"code": "invalid_email"
}
]
}
}{
"error": {
"code": "unknown_api_key",
"message": "The API key you presented doesn't match any active key.",
"type": "auth_error",
"doc_url": "https://portal.ledgersyncappv2.com/errors/unknown_api_key",
"category": "AUTH_ERROR",
"is_user_actionable": true,
"source_diagnostic_code": "FIN-103",
"param": "client.email",
"trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
"errors": [
{
"param": "client.email",
"message": "must be a valid email address",
"code": "invalid_email"
}
]
}
}{
"error": {
"code": "unknown_api_key",
"message": "The API key you presented doesn't match any active key.",
"type": "auth_error",
"doc_url": "https://portal.ledgersyncappv2.com/errors/unknown_api_key",
"category": "AUTH_ERROR",
"is_user_actionable": true,
"source_diagnostic_code": "FIN-103",
"param": "client.email",
"trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
"errors": [
{
"param": "client.email",
"message": "must be a valid email address",
"code": "invalid_email"
}
]
}
}Authorizations
Pass your secret key in the Authorization header as a Bearer
token: Authorization: Bearer sk_test_... (sandbox) or
Bearer sk_live_... (production).
Keys are created in the developer portal and the plaintext secret is shown exactly once at creation. Treat them like passwords — never embed them in mobile apps or front-end code.
Path Parameters
Canonical Connection id (con_<SOURCE>_<bankAccountId>) of the
connection to repair. The placeholder UUID from initiate and
any con_PDF_* id are rejected with 400.
"con_FINICITY_41294"
Body
No body. Send Content-Length: 0 so the upstream load
balancer doesn't reject the request with 411.
The body is of type object | null.
Response
A hosted reauthorization URL, plus the source it leads to.
A different source means the URL starts a new connection.
A matching source targets repair of the existing connection,
but does not guarantee stable ids if Finicity changes the login.
Result of POST /v3/connections/{connection_id}/reauthorize: the
connection id (echoed for correlation), the source the hosted URL
actually leads to, and a reauthorize action carrying that URL.
"con_FINICITY_41294"
The source the hosted URL leads to. Usually the same source as
the connection_id you passed. Repairs aim to retain existing
ids, but a matching source does not guarantee this: Finicity
may issue a different login that cannot be matched back to the
existing connection.
When it differs, the URL hands the end-user to a different
provider (today: a Finicity connection at a bank we now serve
exclusively through MX). Completing it creates a new
connection with a new con_ id and new acc_ / txn_ ids
under the same Client; the old connection is not repaired.
Compare this against the source segment of the
connection_id you sent, and if it differs, re-list
GET /v3/clients/{client_id}/connections and
GET /v3/accounts?client_id=...
after the connection.active webhook rather than expecting
the old ids to come back.
FINICITY, MX, FDE, PDF "FINICITY"
Show child attributes
Show child attributes
