Connecting accounts
Build your own picker for Facebook Pages, Instagram accounts, LinkedIn pages, Pinterest boards, business locations and more, after the user approves on the platform.
Some platforms grant access to a person, not to the thing you want to post as. After a Facebook login you still need to know which Page; after a Google login, which business location. By default Pozy shows a picker for this step. In headless mode you build the picker in your own app and Pozy only handles the OAuth around it.
Use headless mode when the connect has to look native inside your product, when you want to filter the choices (for example, only locations in one country), or when you connect accounts on behalf of users who never see a Pozy page.
#Platforms with a selection step
The default Instagram login (instagram_login) has no selection step.
#How the flow works
#Start with headless=true
Add headless=true and a redirect_url to the connect call. Headless mode needs a redirect_url: without one, Pozy falls back to its own picker. It cannot be combined with welcome=true or handoff=true.
curl "https://api.pozy.ai/v1/connect/facebook?profileId=6650c1f2a9e4b7d3c8a1f0e2&headless=true&redirect_url=https://app.example.com/connect/facebook" \
-H "Authorization: Bearer $POZY_API_KEY"Send the browser to the returned authUrl.
#Read the callback
After consent, the browser lands on your redirect_url with the data your picker needs instead of a finished account:
https://app.example.com/connect/facebook?profileId=6650c1f2a9e4b7d3c8a1f0e2&platform=facebook&step=select_page&tempToken=...&userProfile=...&connect_token=...| Parameter | What it is |
|---|---|
step |
Which selection to show. See the table above. |
tempToken |
A short-lived token for this one flow. Pass it to the list and connect calls. |
userProfile |
JSON describing the person who logged in. Pass it back unchanged on the connect call. |
pendingDataToken |
Sent instead of the full lists when they are too long for a URL (LinkedIn, Google Business Profile, Slack, Google Analytics, and some Pinterest and Snapchat flows). |
connect_token |
Send it as the X-Connect-Token header on the list and connect calls. Pinterest and Snapchat require it. |
userProfile arrives URL-encoded JSON. Depending on your framework it may still be encoded once after the query string is parsed, so parse defensively:
const raw = url.searchParams.get("userProfile") ?? "{}";
const userProfile = JSON.parse(raw.trim().startsWith("{") ? raw : decodeURIComponent(raw));#List the options
From your server, call the list endpoint for that step with your API key:
curl "https://api.pozy.ai/v1/connect/facebook/select-page?profileId=6650c1f2a9e4b7d3c8a1f0e2&tempToken=$TEMP_TOKEN" \
-H "Authorization: Bearer $POZY_API_KEY" \
-H "X-Connect-Token: $CONNECT_TOKEN"const res = await fetch(`https://api.pozy.ai/v1/connect/facebook/select-page?${new URLSearchParams({ profileId: "6650c1f2a9e4b7d3c8a1f0e2", tempToken })}`, {
headers: {
Authorization: `Bearer ${process.env.POZY_API_KEY}`,
"X-Connect-Token": connectToken,
},
});
const data = await res.json();
if (!res.ok) throw new Error(data.error);
// data.pages: [{ id, name, username, category }]resp = requests.get(
"https://api.pozy.ai/v1/connect/facebook/select-page",
headers={
"Authorization": f"Bearer {os.environ['POZY_API_KEY']}",
"X-Connect-Token": connect_token,
},
params={"profileId": "6650c1f2a9e4b7d3c8a1f0e2", "tempToken": temp_token},
timeout=30,
)
pages = resp.json()["pages"]{
"pages": [
{ "id": "104857221093311", "name": "Acme Coffee", "username": "acmecoffee", "category": "Coffee shop" },
{ "id": "104857221093312", "name": "Acme Coffee Madrid", "username": "acmemadrid", "category": "Cafe" }
],
"truncated": false
}#Show your picker
Render the options however you like. Keep the tempToken, userProfile and connect_token in your server-side session, not in hidden form fields.
#Connect the choice
Post the choice back. The response contains the new account, and a redirect_url (your own, with connected, accountId and the other result parameters added) you can send the browser to.
curl -X POST "https://api.pozy.ai/v1/connect/facebook/select-page" \
-H "Authorization: Bearer $POZY_API_KEY" \
-H "X-Connect-Token: $CONNECT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"profileId": "6650c1f2a9e4b7d3c8a1f0e2",
"pageIds": ["104857221093311", "104857221093312"],
"tempToken": "'"$TEMP_TOKEN"'",
"userProfile": { "id": "10228841765", "name": "Ana Ruiz" },
"redirect_url": "https://app.example.com/connected"
}'const resultRes = await fetch("https://api.pozy.ai/v1/connect/facebook/select-page", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.POZY_API_KEY}`,
"X-Connect-Token": connectToken,
"Content-Type": "application/json",
},
body: JSON.stringify({
profileId: "6650c1f2a9e4b7d3c8a1f0e2",
pageIds: ["104857221093311", "104857221093312"],
tempToken,
userProfile,
redirect_url: "https://app.example.com/connected",
}),
});
const result = await resultRes.json();
if (!resultRes.ok) throw new Error(result.error);
// result.accounts: one entry per connected Page; result.failed: Pages that could not connectresp = requests.post(
"https://api.pozy.ai/v1/connect/facebook/select-page",
headers={
"Authorization": f"Bearer {os.environ['POZY_API_KEY']}",
"X-Connect-Token": connect_token,
},
json={
"profileId": "6650c1f2a9e4b7d3c8a1f0e2",
"pageIds": ["104857221093311", "104857221093312"],
"tempToken": temp_token,
"userProfile": user_profile,
"redirect_url": "https://app.example.com/connected",
},
timeout=30,
)
result = resp.json(){
"message": "Facebook page connected successfully",
"redirect_url": "https://app.example.com/connected?connected=facebook&profileId=6650c1f2a9e4b7d3c8a1f0e2&accountId=6651d3a7b2c9e4f1a0b8c7d6&stage=select_page",
"account": {
"accountId": "6651d3a7b2c9e4f1a0b8c7d6",
"platform": "facebook",
"username": "acmecoffee",
"isActive": true,
"selectedPageName": "Acme Coffee"
},
"accounts": [
{ "accountId": "6651d3a7b2c9e4f1a0b8c7d6", "platform": "facebook", "username": "acmecoffee" },
{ "accountId": "6651d3a7b2c9e4f1a0b8c7d7", "platform": "facebook", "username": "acmemadrid" }
],
"failed": []
}#Several choices at once
Facebook Pages, Instagram accounts, LinkedIn pages, business locations and Slack channels can be connected in one call. Each choice becomes its own account.
| Platform | Single | Several |
|---|---|---|
| Facebook, Instagram | pageId |
pageIds |
accountType + selectedOrganization |
selections: a list of { accountType, selectedOrganization } |
|
| Google Business Profile | locationId |
locations: a list of { locationId } |
| Slack | channelId |
channelIds |
The response lists what connected in accounts and what did not in failed, each with a code and message. One failed Page does not stop the others.
#LinkedIn: personal profile or company page
LinkedIn returns a pendingDataToken. Read the organizations with GET /v1/connect/pending-data?token=..., then fetch logos and vanity names for them with GET /v1/connect/linkedin/organizations?tempToken=...&orgIds=.... Offer the user's personal profile as the first option.
{
"profileId": "6650c1f2a9e4b7d3c8a1f0e2",
"tempToken": "...",
"userProfile": { "id": "Xk3pq9", "name": "Ana Ruiz" },
"selections": [
{ "accountType": "personal" },
{
"accountType": "organization",
"selectedOrganization": { "id": "90412233", "urn": "urn:li:organization:90412233", "name": "Acme Coffee" }
}
],
"redirect_url": "https://app.example.com/connected"
}#Timing
| Token | Lifetime |
|---|---|
| The whole connect flow | 30 minutes from the connect call |
pendingDataToken |
Readable for 1 hour, as often as you like, until the selection completes |
After the selection completes, the tokens stop working. If the user abandons your picker, start a new connect.
#Change a selection later
You do not need a new OAuth to switch the Page, organization, board or location behind an existing account:
| Platform | Endpoint |
|---|---|
PUT /v1/accounts/{accountId}/facebook-page with selectedPageId |
|
PUT /v1/accounts/{accountId}/linkedin-organization |
|
PUT /v1/accounts/{accountId}/pinterest-boards |
|
| Google Business Profile | PUT /v1/accounts/{accountId}/gmb-locations |
Each has a matching GET that lists the options the account can switch to.