# Hosted connect pages

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](https://pozy.ai/docs/connect/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.

```bash cURL
curl "https://api.pozy.ai/v1/connect/instagram?profileId=6650c1f2a9e4b7d3c8a1f0e2&redirect_url=https://app.example.com/connected" \
  -H "Authorization: Bearer $POZY_API_KEY"
```
```ts Node
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)
```
```python Python
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"]
```

```json
{
  "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](https://pozy.ai/docs/connect/credentials#wordpress-and-woocommerce). |
| 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](https://pozy.ai/docs/ads). |
| Bluesky | No OAuth | See [Credentials and bots](https://pozy.ai/docs/connect/credentials). |
| 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](https://pozy.ai/docs/connect/scopes). |
| `reconnectAccountId` | Reconnect this existing account. Its `accountId` and history stay the same. |
| `oauthAppId` | Run the connect through [your own platform app](https://pozy.ai/docs/connect/own-apps). 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](https://pozy.ai/docs/connect/headless). |
| `welcome`, `brandName`, `logoUrl`, `primaryColor`, `welcomeTheme` | Show and style the [welcome screen](https://pozy.ai/docs/connect/welcome-screen). |
| `handoff` | `true` to get a short link for a QR code. See [Phone handoff](https://pozy.ai/docs/connect/phone-handoff). |

The full list, with types and limits, is on [Get OAuth connect URL](https://pozy.ai/docs/api/getConnectUrl).

## What the user sees

1. **Welcome screen** (optional): "Acme wants to connect to your Instagram", with your logo. It forwards to the platform after about a second.
2. **The platform's consent screen**: sign in, review the permissions, approve.
3. **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.
4. **Back to your app**: the browser goes to `redirect_url`. With no `redirect_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.

```ts
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");
});
```

> **Do not trust the redirect alone:**
> 
> 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.

```text
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](https://pozy.ai/docs/connect#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`:

```bash
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](https://pozy.ai/docs/connect/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](https://pozy.ai/docs/build/profiles) and pass that `profileId`. Accounts never mix between customers.
- Turn on the [welcome screen](https://pozy.ai/docs/connect/welcome-screen) with your brand, so the first thing they see is your name.
- [Your own platform apps](https://pozy.ai/docs/connect/own-apps), coming soon, will put your app's name on the platform's consent screen too.
- Route `account.connected` to the right customer with the event's `profiles[].externalId`. See [Route webhooks to tenants](https://pozy.ai/docs/build/route-webhooks).

## Endpoints

| Endpoint | What it does |
| --- | --- |
| [`GET /v1/connect/{platform}`](https://pozy.ai/docs/api/getConnectUrl) | Get OAuth connect URL |
| [`GET /v1/connect/{platform}/ads`](https://pozy.ai/docs/api/connectAds) | Connect ads for a platform |
| [`GET /v1/connect/shopify`](https://pozy.ai/docs/api/getShopifyConnectUrl) | Get Shopify OAuth connect URL |
| [`GET /v1/connect/wordpress`](https://pozy.ai/docs/api/getWordPressAuthUrl) | Get WordPress.com OAuth connect URL |
| [`GET /v1/connect/wix`](https://pozy.ai/docs/api/getWixConnectUrl) | Get Wix site install URL |
| [`GET /v1/connect/webflow`](https://pozy.ai/docs/api/getWebflowConnectUrl) | Get Webflow OAuth URL |
| [`GET /v1/connect/hubspot`](https://pozy.ai/docs/api/getHubSpotConnectUrl) | Get HubSpot OAuth connect URL |
| [`GET /v1/connect/googleanalytics`](https://pozy.ai/docs/api/getGoogleAnalyticsConnectUrl) | Get Google Analytics OAuth connect URL |
| [`GET /v1/accounts`](https://pozy.ai/docs/api/listAccounts) | List accounts |
