Connecting accounts
Start an OAuth connect with one API call, send the user through Pozy's hosted pages and read the result on your redirect URL.
Hosted connect is the quickest way to get a user's account into Pozy. You make one API call, send the user's browser to the URL it returns, and Pozy handles the platform's consent screen, any Page or location picker, and the redirect back to your app.
The hosted pages run on pozy.ai. Add a welcome screen and they carry your name and logo instead of Pozy's.
#Start a connect
GET /v1/connect/{platform} returns an authUrl. profileId is required: it is the profile the new account joins.
curl "https://api.pozy.ai/v1/connect/instagram?profileId=6650c1f2a9e4b7d3c8a1f0e2&redirect_url=https://app.example.com/connected" \
-H "Authorization: Bearer $POZY_API_KEY"const res = await fetch("https://api.pozy.ai/v1/connect/instagram?profileId=6650c1f2a9e4b7d3c8a1f0e2&redirect_url=https:%2F%2Fapp.example.com%2Fconnected", {
headers: { Authorization: `Bearer ${process.env.POZY_API_KEY}` },
});
const data = await res.json();
if (!res.ok) throw new Error(data.error);
// In an Express handler: res.redirect(data.authUrl)import os
import requests
resp = requests.get(
"https://api.pozy.ai/v1/connect/instagram",
headers={"Authorization": f"Bearer {os.environ['POZY_API_KEY']}"},
params={
"profileId": "6650c1f2a9e4b7d3c8a1f0e2",
"redirect_url": "https://app.example.com/connected",
},
timeout=30,
)
resp.raise_for_status()
auth_url = resp.json()["authUrl"]{
"authUrl": "https://www.instagram.com/oauth/authorize?client_id=...",
"state": "c2Vzc2lvbi0..."
}Send the browser to authUrl. Keep state if you want to match the callback to the request, but treat it as opaque. The link and the flow behind it expire after 30 minutes.
Call this endpoint from your server. The key in the Authorization header must never reach the browser.
#Platforms
{platform} is one of facebook, instagram, linkedin, twitter (X), tiktok, youtube, threads, pinterest, googlebusiness, snapchat, reddit, slack, whatsapp, google and microsoft.
A few platforms have their own start endpoint because they need extra input or behave differently:
| Platform | Start with | Notes |
|---|---|---|
| Shopify | GET /v1/connect/shopify?shop=your-store.myshopify.com |
The store domain is required. |
| WordPress.com | GET /v1/connect/wordpress |
Self-hosted sites use an application password. |
| Wix | GET /v1/connect/wix |
Returns the Wix app install link. |
| Webflow | GET /v1/connect/webflow |
The user picks exactly one site. |
| HubSpot | GET /v1/connect/hubspot |
One portal per profile. |
| Google Analytics | GET /v1/connect/googleanalytics |
Each GA4 property becomes its own account. |
| Ad networks | GET /v1/connect/{platform}/ads |
Meta, LinkedIn, TikTok, X, Pinterest, Google Ads and Reddit Ads. See Ads. |
| Bluesky | No OAuth | See Credentials and bots. |
| Telegram, Discord | Coming soon | Not available to connect yet: the connect URL answers 403 feature_not_available. |
#Parameters
| Parameter | What it does |
|---|---|
profileId |
Required. The profile the account joins. A profile-scoped key can only use its own profiles. |
redirect_url |
Where the browser goes when the flow ends. An absolute https:// URL, plain http:// only for localhost (for local development), or an app scheme such as myapp://connected. Your own query parameters are kept. |
scopes |
Comma-separated permission areas, for example posting,analytics. See Scopes. |
reconnectAccountId |
Reconnect this existing account. Its accountId and history stay the same. |
oauthAppId |
Run the connect through your own platform app. Coming soon. |
loginHint |
An email address to preselect in the Google or Microsoft account picker. |
loginMethod |
Instagram only: instagram_login (default) or facebook_login. Facebook Login adds a Page picker. |
headless |
true to skip Pozy's picker and build your own. See Headless selection. |
welcome, brandName, logoUrl, primaryColor, welcomeTheme |
Show and style the welcome screen. |
handoff |
true to get a short link for a QR code. See Phone handoff. |
The full list, with types and limits, is on Get OAuth connect URL.
#What the user sees
- Welcome screen (optional): "Acme wants to connect to your Instagram", with your logo. It forwards to the platform after about a second.
- The platform's consent screen: sign in, review the permissions, approve.
- Picker (some platforms): choose the Facebook Pages, LinkedIn page, Pinterest board, business locations, WhatsApp number or Slack channels to connect. Several can be ticked where the platform allows it, and each becomes its own account.
- Back to your app: the browser goes to
redirect_url. With noredirect_url, the user sees a "You're connected" screen they can close.
Connects started from the Pozy dashboard return to Connections in the dashboard instead.
#Read the result
On success, Pozy adds these parameters to your redirect_url:
| Parameter | Value |
|---|---|
connected |
The platform id, for example instagram. |
profileId |
The profile the account joined. |
accountId |
The new or reconnected account. Store this. |
username |
The account's handle, Page name, store domain or mailbox address. |
stage |
Where the flow ended: callback, or the picker step such as select_page. |
request_id |
Quote it to support if something looks wrong. |
When a picker connects several accounts at once, the redirect names the first one. List the profile's accounts, or wait for one account.connected event per account, to get them all.
app.get("/connected", async (req, res) => {
const { connected, accountId, profileId, error, error_message } = req.query;
if (error) {
return res.redirect(`/settings/channels?failed=${encodeURIComponent(String(error_message ?? error))}`);
}
await db.channels.upsert({ accountId, profileId, platform: connected });
res.redirect("/settings/channels?connected=1");
});Anyone can type a URL with accountId=... into a browser. Before you link an account to a user in your database, confirm it: list that user's profile with GET /v1/accounts?profileId=... and check the account is in it, or wait for the account.connected webhook and check that its profileId belongs to that user.
#Errors on the redirect
A cancelled or failed connect sends the browser to redirect_url with error and platform, plus error_message you can show the user when one is available.
https://app.example.com/connected?error=oauth_denied&platform=instagram&is_user_fixable=true&stage=authorize&request_id=req_...error |
Cause | What to do |
|---|---|---|
oauth_denied |
The user cancelled or declined a permission. platform_error and platform_error_description carry the platform's own reason when it sends one. |
Let them try again. |
no_facebook_pages |
No Facebook Page was shared. error_reason says why: pages_permission_declined, no_pages_granted or granted_pages_not_listed. |
Connect again, choose "Edit previous settings" in Meta's dialog and tick a Page. |
invalid_state |
The flow took longer than 30 minutes or the link was used twice. | Request a fresh connect URL. |
connection_failed |
The platform refused the authorization code. | Start again. |
payment_required |
Billing blocks the connect. | Send the workspace owner to billing. |
temporarily_unavailable |
A platform service Pozy relies on cannot take new connections right now. Nothing is wrong on your side. | Try again later. |
reconnect_required |
This account must be reconnected from its own settings. | Use reconnectAccountId. |
is_user_fixable=true means connecting again usually fixes it. Treat any error you do not recognise as a generic failure: new values can appear.
Errors that happen before the user leaves your app come back as a normal API error instead:
| Status | code |
Cause |
|---|---|---|
| 400 | INVALID_REDIRECT_URL |
redirect_url is relative, or uses plain http:// for a host other than localhost. Use an https:// URL or an app scheme. |
| 400 | missing_field |
profileId is missing. |
| 400 | invalid_field_value |
A bad scopes value, or welcome and handoff options that cannot be combined. param names the field. |
| 402 | payment_required |
See billing gates. |
| 404 | profile_not_found |
The profile does not exist in this workspace. |
| 503 | temporarily_unavailable |
A platform service Pozy relies on cannot take new connections right now. Retry later. |
#Reconnect an account
Tokens expire and users revoke access. To repair an account, start a connect for the same platform with reconnectAccountId:
curl "https://api.pozy.ai/v1/connect/instagram?profileId=6650c1f2a9e4b7d3c8a1f0e2&reconnectAccountId=6651d3a7b2c9e4f1a0b8c7d6&redirect_url=https://app.example.com/connected" \
-H "Authorization: Bearer $POZY_API_KEY"The account keeps its id, its posts and its analytics history. Account health tells you which accounts need it.
#White-label notes
If your customers connect their own accounts through your product:
- Create one profile per customer and pass that
profileId. Accounts never mix between customers. - Turn on the welcome screen with your brand, so the first thing they see is your name.
- Your own platform apps, coming soon, will put your app's name on the platform's consent screen too.
- Route
account.connectedto the right customer with the event'sprofiles[].externalId. See Route webhooks to tenants.
#Endpoints
| Endpoint | What it does |
|---|---|
GET /v1/connect/{platform} |
Get OAuth connect URL |
GET /v1/connect/{platform}/ads |
Connect ads for a platform |
GET /v1/connect/shopify |
Get Shopify OAuth connect URL |
GET /v1/connect/wordpress |
Get WordPress.com OAuth connect URL |
GET /v1/connect/wix |
Get Wix site install URL |
GET /v1/connect/webflow |
Get Webflow OAuth URL |
GET /v1/connect/hubspot |
Get HubSpot OAuth connect URL |
GET /v1/connect/googleanalytics |
Get Google Analytics OAuth connect URL |
GET /v1/accounts |
List accounts |