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.
| Method | Used for | Browser behavior |
|---|---|---|
| Dashboard | You / your team | Onepostly Connections page |
API + redirectUrl | OAuth connections | Your app |
| API + App Password | Bluesky connections | No OAuth redirect |
Connect From The Dashboard
- Open Connections in the app.
- Pick a platform and complete its connection flow.
- 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
redirectUrlis required and must be absolutehttps(local dev exceptionshttp://localhost,127.0.0.1)- Requires a
read_writekey - Optional
reconnectre-authorizes an existing connection without consuming a plan slot
Handle the redirect
On success the browser returns to your URL:
| Param | Meaning |
|---|---|
connected | Platform id (x, instagram, ...) |
accountId | Pass this as destinations[].accountId when publishing |
handle | Account handle when available |
count | Accounts saved in this flow |
reconnected | 1 if this was a reconnect |
skipped | New 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.
| Parameter | Type | Omitted |
|---|---|---|
limit | integer 1-500 | 500 connections per page |
page | integer ≥ 1 | 1, the first page |
profile | string ≤ 60 | All profiles |
platform | string ≤ 64 | All platforms |
addedWithinDays | integer 1-3650 | Any age |
sort | newest or oldest | newest |
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.
page | limit | Returns | total | hasMore |
|---|---|---|---|---|
| 1 | 500 | First 500 | 2000 | true |
| 2 | 500 | Next 500 | 2000 | true |
| 4 | 500 | Last 500 | 2000 | false |
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:
| Param | Meaning |
|---|---|
platform | facebook |
step | select_page |
tempToken | Short-lived session for the two calls below |
userProfile | URL-encoded JSON { "id", "name" } |
organizationId | Workspace 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:
| Param | Meaning |
|---|---|
platform | instagram |
step | select_instagram_account |
tempToken | Short-lived session for the two calls below |
userProfile | URL-encoded JSON { "id", "name" } |
organizationId | Workspace 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
| Code | HTTP | Cause | Fix |
|---|---|---|---|
REDIRECT_URL_REQUIRED | 400 | API-key start (or headless=true) without redirectUrl | Add redirectUrl |
INVALID_REDIRECT_URL | 400 | Bad scheme or malformed URL | Use absolute https |
INVALID_TEMP_TOKEN / TEMP_TOKEN_EXPIRED | 400 | Facebook tempToken bad or expired | Restart the OAuth flow |
PAGE_NOT_FOUND | 404 | Selected Page not on the token | Refresh the Pages list |
CONNECTION_LIMIT_REACHED | 403 | Workspace hit its plan cap | Upgrade the plan or disconnect an account |
FORBIDDEN | 403 | Read-only key used to start OAuth | Use a read_write key |
NOT_CONFIGURED | 503 | Platform OAuth unavailable on this deployment | Contact support |
Next step. Publish your first post.