Connection

Connect social accounts from the dashboard or programmatically with OAuth and platform-specific connection flows.

Before you can publish, connect each social account to your workspace. Use the dashboard for your own accounts, or the API for programmatic connections.

MethodUsed forBrowser behavior
DashboardYou / your teamOnepostly Connections page
API + redirectUrlOAuth connectionsYour app
API + App PasswordBluesky connectionsNo OAuth redirect

Connect From The Dashboard

  1. Open Connections in the app.
  2. Pick a platform and complete its connection flow.
  3. Copy the account ID from the row's copy button, or use the connection API.

Facebook. After the consent screen you pick exactly one Page. Each connected Page is its own connection, so connect again to add another Page.

Instagram. Pick Instagram Login (direct, fastest) or Facebook Login (through a Page with a linked Instagram account). Facebook Login adds a second step where you pick the linked account.

Connect Accounts Programmatically

The API supports every platform. Choose the connection flow that matches the platform.

1. OAuth Flow Connections

Use OAuth for x, instagram, facebook, threads, linkedin, youtube, tiktok, and pinterest.

Start the OAuth flow

The SDK returns the authorization URL. Send the user's browser to it.

import { Configuration, ConnectionsApi } from "@onepostly/sdk";

const connectionsApi = new ConnectionsApi(
  new Configuration({ apiKey: process.env.ONEPOSTLY_API_KEY })
);

const response = await connectionsApi.startOAuth({
  platform: "x",
  redirectUrl: "https://app.onepostly.com/connections/callback",
  profile: "Business",
});
console.log(response);
{
  "url": "https://x.com/i/oauth2/authorize?response_type=code&client_id=YOUR_CLIENT_ID&state=abc123"
}

Open url in the browser, full page or popup. This is the platform's own consent screen.

OAuth Rules

  • redirectUrl is required and must be absolute https (local dev exceptions http://localhost, 127.0.0.1)
  • Requires a read_write key
  • Optional reconnect re-authorizes an existing connection without consuming a plan slot

Handle the redirect

On success the browser returns to your URL:

ParamMeaning
connectedPlatform id (x, instagram, ...)
accountIdPass this as destinations[].accountId when publishing
handleAccount handle when available
countAccounts saved in this flow
reconnected1 if this was a reconnect
skippedNew accounts skipped because the workspace hit its plan limit

A success redirect looks like https://yourapp.com/oauth/onepostly?connected=x&accountId=ACCOUNT_ID&handle=%40you&count=1

On failure the only param is error, a human-readable reason you can show the user.

Verify the connection

import { Configuration, ConnectionsApi } from "@onepostly/sdk";

const connectionsApi = new ConnectionsApi(
  new Configuration({ apiKey: process.env.ONEPOSTLY_API_KEY })
);

const response = await connectionsApi.listConnections({
  limit: 20,
  page: 2,
  profile: "Business",
  platform: "instagram",
  addedWithinDays: 90,
  sort: "newest",
});
console.log(response);
{
  "connections": [
    {
      "id": "a1b2c3d4-e5f6-4a7b-8c9d-e0f1a2b3c4d5",
      "platform": "instagram",
      "displayName": "Onepostly",
      "handle": "@onepostly",
      "avatarUrl": "https://cdn.onepostly.com/avatars/onepostly.jpg",
      "status": "active",
      "tokenExpiresAt": "2026-10-25T10:00:00.000Z",
      "canAutoRenew": true,
      "authHealth": "healthy",
      "profileName": "Business",
      "xSubscriptionType": "Premium",
      "createdAt": "2026-08-25T10:00:00.000Z",
      "updatedAt": "2026-08-25T10:00:00.000Z"
    }
  ],
  "total": 2000,
  "page": 1,
  "hasMore": true
}

Every parameter is optional. Omitting one applies no filter for it.

ParameterTypeOmitted
limitinteger 1-500500 connections per page
pageinteger ≥ 11, the first page
profilestring ≤ 60All profiles
platformstring ≤ 64All platforms
addedWithinDaysinteger 1-3650Any age
sortnewest or oldestnewest

Filters combine with AND, so platform=x&profile=Business returns the X accounts in the Business profile. An unknown profile name matches nothing and returns an empty list.

Paging

Each response carries at most limit connections, 500 by default. page selects which slice to return, starting at 1. Read total for the workspace count and hasMore to know whether another page follows.

A workspace with 2000 connections and no parameters returns the first 500 and page 1. Request page 2 for the next 500, and so on until hasMore is false.

pagelimitReturnstotalhasMore
1500First 5002000true
2500Next 5002000true
4500Last 5002000false

Keep requesting page page + 1 while hasMore is true. A workspace with fewer connections than the page size returns them all on page 1 with hasMore set to false.

The response wraps the page.

Prop

Type

Each Connection returns the following fields.

Prop

Type

Facebook Page selection

Facebook has an extra step. The user selects one Page, and that Page becomes one connection. Two variants:

Hosted (default)

Omit headless. After the consent screen, Onepostly shows its own Page picker, then redirects to your redirectUrl (or the dashboard) with the usual success params above.

Headless. Build your own picker

Pass headless=true. After the consent screen the browser returns immediately to your redirectUrl with:

ParamMeaning
platformfacebook
stepselect_page
tempTokenShort-lived session for the two calls below
userProfileURL-encoded JSON { "id", "name" }
organizationIdWorkspace id

List the Pages:

import { Configuration, ConnectionsApi } from "@onepostly/sdk";

const connectionsApi = new ConnectionsApi(
  new Configuration({ apiKey: process.env.ONEPOSTLY_API_KEY })
);

const response = await connectionsApi.listFacebookPages({
  tempToken: "temp_abc123",
});
console.log(response);
{
  "pages": [
    {
      "id": "123456789012345",
      "name": "Onepostly",
      "username": "onepostly",
      "category": "Business",
      "pictureUrl": "https://cdn.onepostly.com/avatars/onepostly.jpg"
    }
  ]
}

Then complete the selection:

import { Configuration, ConnectionsApi } from "@onepostly/sdk";

const connectionsApi = new ConnectionsApi(
  new Configuration({ apiKey: process.env.ONEPOSTLY_API_KEY })
);

const response = await connectionsApi.selectFacebookPage({
  body: {
    "tempToken": "temp_abc123",
    "pageId": "123456789012345",
    "redirectUrl": "https://app.onepostly.com/connections/callback"
  },
});
console.log(response);
{
  "connection": {
    "id": "a1b2c3d4-e5f6-4a7b-8c9d-e0f1a2b3c4d5",
    "platform": "facebook",
    "displayName": "Onepostly",
    "handle": "@onepostly",
    "avatarUrl": "https://cdn.onepostly.com/avatars/onepostly.jpg",
    "status": "active",
    "tokenExpiresAt": "2026-10-25T10:00:00.000Z",
    "canAutoRenew": true,
    "authHealth": "healthy",
    "profileName": "Business",
    "xSubscriptionType": "Premium",
    "createdAt": "2026-08-25T10:00:00.000Z",
    "updatedAt": "2026-08-25T10:00:00.000Z"
  },
  "redirectUrl": "https://app.onepostly.com/connections/callback"
}

Send the browser to the returned redirectUrl to finish. Reconnecting an existing Facebook connection skips the picker and re-binds the same Page automatically.

Instagram account selection (Facebook Login only)

Start Instagram OAuth with loginMethod=facebook_login:

curl "https://api.onepostly.com/v1/connections/oauth/instagram/start?loginMethod=facebook_login&redirectUrl=https://yourapp.com/oauth/onepostly" \
  -H "x-api-key: op_YOUR_KEY"

After the consent screen the user picks one linked Instagram account, and that account becomes one connection. Two variants, mirroring Facebook:

Hosted (default)

Omit headless. After consent, Onepostly shows its own account picker, then redirects to your redirectUrl with the usual success params above.

Headless. Build your own picker

Pass headless=true. After consent the browser returns immediately to your redirectUrl with:

ParamMeaning
platforminstagram
stepselect_instagram_account
tempTokenShort-lived session for the two calls below
userProfileURL-encoded JSON { "id", "name" }
organizationIdWorkspace id

List the linked accounts:

import { Configuration, ConnectionsApi } from "@onepostly/sdk";

const connectionsApi = new ConnectionsApi(
  new Configuration({ apiKey: process.env.ONEPOSTLY_API_KEY })
);

const response = await connectionsApi.listInstagramAccounts({
  tempToken: "temp_abc123",
});
console.log(response);
{
  "accounts": [
    {
      "pageId": "123456789012345",
      "pageName": "Onepostly",
      "instagramAccountId": "17841400008460056",
      "username": "onepostly",
      "name": "Onepostly",
      "profilePictureUrl": "https://cdn.onepostly.com/avatars/onepostly.jpg"
    }
  ]
}

Then complete the selection:

import { Configuration, ConnectionsApi } from "@onepostly/sdk";

const connectionsApi = new ConnectionsApi(
  new Configuration({ apiKey: process.env.ONEPOSTLY_API_KEY })
);

const response = await connectionsApi.selectInstagramAccount({
  body: {
    "tempToken": "temp_abc123",
    "instagramAccountId": "17841400008460056",
    "redirectUrl": "https://app.onepostly.com/connections/callback"
  },
});
console.log(response);
{
  "connection": {
    "id": "a1b2c3d4-e5f6-4a7b-8c9d-e0f1a2b3c4d5",
    "platform": "instagram",
    "displayName": "Onepostly",
    "handle": "@onepostly",
    "avatarUrl": "https://cdn.onepostly.com/avatars/onepostly.jpg",
    "status": "active",
    "tokenExpiresAt": "2026-10-25T10:00:00.000Z",
    "canAutoRenew": true,
    "authHealth": "healthy",
    "profileName": "Business",
    "xSubscriptionType": "Premium",
    "createdAt": "2026-08-25T10:00:00.000Z",
    "updatedAt": "2026-08-25T10:00:00.000Z"
  },
  "redirectUrl": "https://app.onepostly.com/connections/callback"
}

Send the browser to the returned redirectUrl to finish. Reconnecting an existing Instagram (Facebook Login) connection skips the picker and re-binds the same account automatically. Omit loginMethod (or pass instagram_login) for the direct flow with no second step.

2. Bluesky App Password Connections

Bluesky does not use an OAuth redirect. A Bluesky handle or email and an App Password are required. Once the connection is created, Onepostly never stores the App Password.

Create an App Password in Bluesky → Settings → Privacy and Security → App Passwords. App Passwords can be revoked from Bluesky at any time.

Connect Bluesky

import { Configuration, ConnectionsApi } from "@onepostly/sdk";

const connectionsApi = new ConnectionsApi(
  new Configuration({ apiKey: process.env.ONEPOSTLY_API_KEY })
);

const response = await connectionsApi.connectBluesky({
  body: {
    "identifier": "alice.bsky.social",
    "appPassword": "abcd-efgh-ijkl-mnop",
    "profileName": "Business"
  },
});
console.log(response);
{
  "connection": {
    "id": "a1b2c3d4-e5f6-4a7b-8c9d-e0f1a2b3c4d5",
    "platform": "bluesky",
    "displayName": "Onepostly",
    "handle": "@onepostly",
    "avatarUrl": "https://cdn.onepostly.com/avatars/onepostly.jpg",
    "status": "active",
    "tokenExpiresAt": "2026-10-25T10:00:00.000Z",
    "canAutoRenew": true,
    "authHealth": "healthy",
    "profileName": "Business",
    "xSubscriptionType": "Premium",
    "createdAt": "2026-08-25T10:00:00.000Z",
    "updatedAt": "2026-08-25T10:00:00.000Z"
  }
}

Invalid credentials return 401 with the code AUTH_FAILED.

Reconnect Bluesky

Use the same call with a new App Password and the existing account id. This reconnects the same account without using another plan slot.

import { Configuration, ConnectionsApi } from "@onepostly/sdk";

const connectionsApi = new ConnectionsApi(
  new Configuration({ apiKey: process.env.ONEPOSTLY_API_KEY })
);

const response = await connectionsApi.connectBluesky({
  body: {
    "identifier": "alice.bsky.social",
    "appPassword": "zzzz-yyyy-xxxx-wwww",
    "profileName": "Business",
    "reconnectId": "a1b2c3d4-e5f6-4a7b-8c9d-e0f1a2b3c4d5"
  },
});
console.log(response);
{
  "connection": {
    "id": "a1b2c3d4-e5f6-4a7b-8c9d-e0f1a2b3c4d5",
    "platform": "bluesky",
    "displayName": "Onepostly",
    "handle": "@onepostly",
    "avatarUrl": "https://cdn.onepostly.com/avatars/onepostly.jpg",
    "status": "active",
    "tokenExpiresAt": "2026-10-25T10:00:00.000Z",
    "canAutoRenew": true,
    "authHealth": "healthy",
    "profileName": "Business",
    "xSubscriptionType": "Premium",
    "createdAt": "2026-08-25T10:00:00.000Z",
    "updatedAt": "2026-08-25T10:00:00.000Z"
  }
}

Bluesky Default Languages

Read or change the account default post languages (1–3 BCP-47 codes) applied at publish time whenever a post omits per-post langs. Explicit null clears the default.

import { Configuration, ConnectionsApi } from "@onepostly/sdk";

const connectionsApi = new ConnectionsApi(
  new Configuration({ apiKey: process.env.ONEPOSTLY_API_KEY })
);

const response = await connectionsApi.getBlueskySettings({
  id: "a1b2c3d4-e5f6-4a7b-8c9d-e0f1a2b3c4d5",
});
console.log(response);
{
  "accountId": "a1b2c3d4-e5f6-4a7b-8c9d-e0f1a2b3c4d5",
  "platform": "bluesky",
  "defaultLangs": [
    "en"
  ],
  "fetchedAt": "2026-08-25T10:00:00.000Z"
}
import { Configuration, ConnectionsApi } from "@onepostly/sdk";

const connectionsApi = new ConnectionsApi(
  new Configuration({ apiKey: process.env.ONEPOSTLY_API_KEY })
);

const response = await connectionsApi.updateBlueskySettings({
  id: "a1b2c3d4-e5f6-4a7b-8c9d-e0f1a2b3c4d5",
  body: {
    "defaultLangs": [
      "en"
    ]
  },
});
console.log(response);
{
  "accountId": "a1b2c3d4-e5f6-4a7b-8c9d-e0f1a2b3c4d5",
  "platform": "bluesky",
  "defaultLangs": [
    "en"
  ],
  "updatedAt": "2026-08-25T10:00:00.000Z"
}

Errors

CodeHTTPCauseFix
REDIRECT_URL_REQUIRED400API-key start (or headless=true) without redirectUrlAdd redirectUrl
INVALID_REDIRECT_URL400Bad scheme or malformed URLUse absolute https
INVALID_TEMP_TOKEN / TEMP_TOKEN_EXPIRED400Facebook tempToken bad or expiredRestart the OAuth flow
PAGE_NOT_FOUND404Selected Page not on the tokenRefresh the Pages list
CONNECTION_LIMIT_REACHED403Workspace hit its plan capUpgrade the plan or disconnect an account
FORBIDDEN403Read-only key used to start OAuthUse a read_write key
NOT_CONFIGURED503Platform OAuth unavailable on this deploymentContact support

Next step. Publish your first post.