curl --request POST \
--url https://api.synci.io/managed/v1/users/{userId}/portal-sessions \
--header 'Authorization: Basic <encoded-value>' \
--header 'Content-Type: application/json' \
--data '
{
"return_url": "<string>",
"institution_id": 4503599627370496,
"connection_id": 4503599627370496
}
'const options = {
method: 'POST',
headers: {Authorization: 'Basic <encoded-value>', 'Content-Type': 'application/json'},
body: JSON.stringify({
return_url: '<string>',
institution_id: 4503599627370496,
connection_id: 4503599627370496
})
};
fetch('https://api.synci.io/managed/v1/users/{userId}/portal-sessions', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.synci.io/managed/v1/users/{userId}/portal-sessions"
payload = {
"return_url": "<string>",
"institution_id": 4503599627370496,
"connection_id": 4503599627370496
}
headers = {
"Authorization": "Basic <encoded-value>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.synci.io/managed/v1/users/{userId}/portal-sessions",
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([
'return_url' => '<string>',
'institution_id' => 4503599627370496,
'connection_id' => 4503599627370496
]),
CURLOPT_HTTPHEADER => [
"Authorization: Basic <encoded-value>",
"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.synci.io/managed/v1/users/{userId}/portal-sessions"
payload := strings.NewReader("{\n \"return_url\": \"<string>\",\n \"institution_id\": 4503599627370496,\n \"connection_id\": 4503599627370496\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Basic <encoded-value>")
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))
}{
"data": {
"url": "<string>",
"expires_at": "<string>"
}
}{
"message": "<string>"
}{
"message": "<string>",
"errors": {}
}Create a portal session
Returns a single-use URL that drops this user straight into the connection portal with no sign-in code — use it when they are already authenticated in your own app. Valid once, for a short window.
This is how managed users create connections: POST /finance/connections
refuses a managed user’s own token. Pass institution_id when the user has
already picked their bank in your app, and the portal skips its picker and
starts that bank’s consent flow itself. Pass connection_id instead to
renew one of the user’s existing connections the same way: it keeps its id
and a connection.reauthorized webhook follows. A user who has not yet accepted the
portal terms is asked to accept first; nothing starts on its own for them.
curl --request POST \
--url https://api.synci.io/managed/v1/users/{userId}/portal-sessions \
--header 'Authorization: Basic <encoded-value>' \
--header 'Content-Type: application/json' \
--data '
{
"return_url": "<string>",
"institution_id": 4503599627370496,
"connection_id": 4503599627370496
}
'const options = {
method: 'POST',
headers: {Authorization: 'Basic <encoded-value>', 'Content-Type': 'application/json'},
body: JSON.stringify({
return_url: '<string>',
institution_id: 4503599627370496,
connection_id: 4503599627370496
})
};
fetch('https://api.synci.io/managed/v1/users/{userId}/portal-sessions', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));import requests
url = "https://api.synci.io/managed/v1/users/{userId}/portal-sessions"
payload = {
"return_url": "<string>",
"institution_id": 4503599627370496,
"connection_id": 4503599627370496
}
headers = {
"Authorization": "Basic <encoded-value>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.synci.io/managed/v1/users/{userId}/portal-sessions",
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([
'return_url' => '<string>',
'institution_id' => 4503599627370496,
'connection_id' => 4503599627370496
]),
CURLOPT_HTTPHEADER => [
"Authorization: Basic <encoded-value>",
"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.synci.io/managed/v1/users/{userId}/portal-sessions"
payload := strings.NewReader("{\n \"return_url\": \"<string>\",\n \"institution_id\": 4503599627370496,\n \"connection_id\": 4503599627370496\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Basic <encoded-value>")
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))
}{
"data": {
"url": "<string>",
"expires_at": "<string>"
}
}{
"message": "<string>"
}{
"message": "<string>",
"errors": {}
}Authorizations
Username = your client id. Password = your client secret.
So the base64-encoded username:password described below is
client_id:client_secret:
Authorization: Basic base64(YOUR_CLIENT_ID:YOUR_CLIENT_SECRET)
Most HTTP clients build that header for you:
curl -u "YOUR_CLIENT_ID:YOUR_CLIENT_SECRET" \
https://api.synci.io/managed/v1/users
Both values come from Developers → Apps → your app. The
secret is shown once at creation and is prefixed oac_.
Path Parameters
Body
Where "Continue to ‹App›" sends this user when they finish. Omit to
use the app's configured portal return URL. With institution_id
or connection_id, a successful bank flow also returns here
directly, with status=completed and connection, even when the
bank finishes in its own app and opens the return in another
browser tab. Any https:// URL is accepted; it does not have to be
registered on the app.
512The institution the user already picked in your app. When set, the
portal shows a short "‹App› uses Synci" interlude and then starts
that institution's consent flow by itself, instead of asking the
user to pick their bank a second time. Ids come from
GET /finance/institutions. This always starts a new connection, even when the user already has
one at that bank; connecting the same login twice gives the user a
second connection with its own copy of the accounts, billed
separately. To renew an existing connection, send connection_id
instead. An institution this app may not connect is rejected with
422 when the session is minted.
1 <= x <= 9007199254740991One of this user's existing connections that needs renewing (an
expiring consent, or a bank that asks the user to sign in again).
The portal shows the same short interlude as for institution_id
and then starts the renewal of THIS connection: it keeps its id,
its accounts and their history, and a connection.reauthorized
webhook follows. Send it as a string. Mutually exclusive with
institution_id, which starts a new connection. Checked when the session is minted: a connection that is not this
user's, or whose provider cannot renew, is a 422.
1 <= x <= 9007199254740991Response
Show child attributes
Show child attributes