Accounts

Connect Accounts

Connect social accounts from the dashboard or from your own app, and list the accounts of your workspace.

Connect a social account to your workspace before you publish to it. Connect your own accounts in the dashboard, or let your users connect theirs from your app.

MethodPlatformsWhere the user signs in
DashboardAllThe Connections page in Onepostly
Connect sessionAll except BlueskyThe platform's sign-in, then back to your app
App PasswordBlueskyNowhere. You send the handle and an App Password

Your first 2 accounts are free. A 3rd account needs a payment method first. See Pricing and Wallet.

Connect From the Dashboard

Open Connections in the dashboard and pick a platform. Only owners and admins of the workspace can connect accounts.

Sign in and approve access. On Facebook, pick a Page. On Instagram, choose Instagram Login, or Facebook Login and pick an account linked to one of your Pages.

Copy the account id from the list, or get it with List Accounts.

Each Facebook Page and each Instagram account is its own account. Repeat the steps to add another.

Connect From Your App

Let your users connect their accounts without leaving your app. This works for every platform except Bluesky and needs a read_write API key.

Create a Connect Session

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

const connectApi = new ConnectApi(
  new Configuration({ accessToken: process.env.ONEPOSTLY_API_KEY })
);

const response = await connectApi.createConnectSession({
  createConnectSessionBody: {
    "platform": "x",
    "redirectUrl": "https://yourapp.com/oauth/onepostly",
    "profile": "Business",
    "metadata": {
      "userId": "user_8412"
    }
  },
});
console.log(response);
{
  "url": "https://x.com/i/oauth2/authorize?response_type=code&state=eyJhbGciOiJIUzI1NiJ9",
  "expiresAt": "2027-03-15T09:10:00.000Z"
}
FieldDescription
platformRequired. Any platform id except bluesky
redirectUrlRequired with an API key. The https URL the browser returns to. http works only on localhost
profileThe profile for the account, created if missing. Defaults to Default
metadataYour own key-value pairs for the account, such as your user's id. Up to 20 keys of 1 to 40 characters, with string values up to 500 characters. See Link Accounts to Your Users
loginMethodInstagram only. instagram_login (default) or facebook_login
headlesstrue to build the Page or account picker yourself

The response has a url that expires after 10 minutes, at expiresAt. A later sign-in returns SESSION_EXPIRED, so create a new session for every attempt.

Past the 2 free accounts without a payment method, this call returns 402 USAGE_BILLING_REQUIRED.

Send the User to the URL

Open the url in the browser, as a full page or a popup. The user signs in on the platform and approves access.

Handle the Redirect

When the user is done, the browser returns to your redirectUrl with these query parameters.

ParameterDescription
connectedThe platform, such as x or instagram
accountIdThe account id. Use it as accountId when you publish
handleThe account handle without a leading @, when the platform has one
reconnected1 when the session came from Reconnect Account
https://yourapp.com/oauth/onepostly?connected=x&accountId=acc_1699f4157fb643f49d2ac447491f32a8&handle=yourbrand

Your own query parameters, such as state, come back unchanged. A #fragment is dropped.

If the connect fails, the redirect has error and error_description instead. See Redirect Errors.

Onepostly does not know who your users are. Send your user's id in metadata when you create the session, as the sample above does. The account keeps it, and every account response and the account.connected webhook return it.

  1. Create the session with metadata, such as { "userId": "user_8412" }.
  2. When the redirect or account.connected arrives, save the account id with that user.
Keep in mindWhy
Handle both the redirect and the webhookThe webhook can arrive before your user is back
Verify with GET /v1/accounts/{id}Anyone can edit the redirect
Two users can connect the same accountBoth get the first user's metadata
Check the account is the user'sYour API key can publish to every account
Clean up on account.disconnectedIt still carries the erased metadata
Keep secrets out of metadataResponses and webhooks return it

Update Metadata

Change an account's metadata at any time with PATCH /v1/accounts/{id}. It needs a read_write key.

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

const accountsApi = new AccountsApi(
  new Configuration({ accessToken: process.env.ONEPOSTLY_API_KEY })
);

const response = await accountsApi.updateAccount({
  id: "acc_1699f4157fb643f49d2ac447491f32a8",
  updateAccountBody: {
    "metadata": {
      "team": "marketing"
    }
  },
});
console.log(response);
{
  "id": "acc_1699f4157fb643f49d2ac447491f32a8",
  "platform": "x",
  "displayName": "Your Brand",
  "handle": "yourbrand",
  "avatarUrl": "https://cdn.onepostly.com/avatars/yourbrand.jpg",
  "status": "active",
  "canAutoRenew": true,
  "tokenExpiresAt": "2027-05-14T10:00:00.000Z",
  "profile": "Business",
  "metadata": {
    "userId": "user_8412",
    "team": "marketing"
  },
  "xSubscriptionType": "None",
  "createdAt": "2027-03-01T10:00:00.000Z",
  "updatedAt": "2027-03-15T10:00:00.000Z"
}
You sendResult
"key": "value"Sets the key
"key": nullRemoves the key
A key you leave outKeeps its value
"metadata": nullRemoves every key

The response is the updated account. An update that would leave more than 20 keys returns 400 VALIDATION_ERROR and changes nothing. Remove keys with null in the same request to make room.

Redirect Errors

A failed connect returns to your redirectUrl with two parameters.

ParameterDescription
errorA stable code from the table below. Build your logic on it
error_descriptionA message you can show the user. The wording can change
https://yourapp.com/oauth/onepostly?error=ACCESS_DENIED&error_description=The+sign-in+was+declined+or+cancelled.
CodeCauseWhat to do
ACCESS_DENIEDThe user declined or cancelledLet the user start again
PLATFORM_ERRORThe platform returned an errorShow error_description, then retry
SESSION_EXPIREDThe sign-in finished after expiresAtCreate a new session
INVALID_CALLBACKNo sign-in code, or a changed sessionCreate a new session
SESSION_MISMATCHThe Onepostly user or workspace changedSign in to the right workspace and retry
FORBIDDENThe Onepostly user is not an owner or adminAsk an owner or admin
PLATFORM_UNAVAILABLEThe platform cannot be connected right nowTry again later
RECONNECT_MISMATCHA reconnect signed in with another accountSign in with the original account
NO_ACCOUNTSNo Page or linked account to connectSet one up on the platform, then retry
USAGE_BILLING_REQUIREDNo payment method past the 2 free accountsAdd a payment method in your dashboard
USAGE_BILLING_FAILEDBilling for the account could not be set upCheck your payment method in the dashboard
AUTHORIZATION_FAILEDThe platform did not finish the sign-inTry again. If it persists, contact support
CONNECT_FAILEDThe account could not be connectedTry again. If it persists, contact support

New codes can be added. Treat a code you do not know like CONNECT_FAILED.

Pick a Page or Account

On Facebook, and on Instagram with Facebook Login, the user picks a Page or a Page-linked account after approving access. Onepostly shows this picker by default.

To build the picker in your own app, create the session with headless: true. Right after the user approves access, the browser returns to your redirectUrl with these parameters instead of the usual ones.

ParameterDescription
platformfacebook or instagram
stepselect_page for Facebook, select_instagram_account for Instagram
connectSessionThe session id for the next two calls. It is valid for 10 minutes

List the user's Pages, or the Instagram accounts linked to them.

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

const connectApi = new ConnectApi(
  new Configuration({ accessToken: process.env.ONEPOSTLY_API_KEY })
);

const response = await connectApi.listConnectCandidates({
  sessionId: "CONNECT_SESSION",
});
console.log(response);
{
  "data": [
    {
      "id": "123456789012345",
      "platform": "facebook",
      "name": "Your Brand",
      "username": "yourbrand",
      "pictureUrl": "https://cdn.onepostly.com/avatars/yourbrand.jpg",
      "category": "Brand",
      "pageName": null
    },
    {
      "id": "123456789012346",
      "platform": "facebook",
      "name": "Your Brand Support",
      "username": "yourbrandsupport",
      "pictureUrl": "https://cdn.onepostly.com/avatars/yourbrandsupport.jpg",
      "category": "Product/service",
      "pageName": null
    }
  ]
}

Each candidate has an id, name, username and pictureUrl. Pages also have a category. Instagram accounts have a pageName, the Page they are linked to.

Send the candidate the user picked.

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

const connectApi = new ConnectApi(
  new Configuration({ accessToken: process.env.ONEPOSTLY_API_KEY })
);

const response = await connectApi.selectConnectCandidate({
  sessionId: "CONNECT_SESSION",
  selectConnectCandidateBody: {
    "candidateId": "123456789012345"
  },
});
console.log(response);
{
  "account": {
    "id": "acc_1699f4157fb643f49d2ac447491f3204",
    "platform": "facebook",
    "displayName": "Your Brand",
    "handle": "yourbrand",
    "avatarUrl": "https://cdn.onepostly.com/avatars/yourbrand.jpg",
    "status": "active",
    "canAutoRenew": true,
    "tokenExpiresAt": "2027-05-14T10:00:00.000Z",
    "profile": "Business",
    "metadata": {
      "userId": "user_8412"
    },
    "createdAt": "2027-03-15T09:00:00.000Z",
    "updatedAt": "2027-03-15T09:00:00.000Z"
  },
  "redirectUrl": "https://yourapp.com/oauth/onepostly?connected=facebook&accountId=acc_1699f4157fb643f49d2ac447491f3204&handle=yourbrand"
}

The response has the new account and a redirectUrl. To finish on a different page than the session's redirectUrl, send redirectUrl in the body.

Send the browser to the returned redirectUrl. It carries the same parameters as a normal redirect.

Connect Bluesky

Bluesky uses an App Password instead of a sign-in screen.

In Bluesky, open Settings → Privacy and Security → App Passwords and create one. You can revoke it there at any time.

Send the handle and the App Password. The account's email or DID also works in place of the handle.

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

const accountsApi = new AccountsApi(
  new Configuration({ accessToken: process.env.ONEPOSTLY_API_KEY })
);

const response = await accountsApi.createAccount({
  createAccountBody: {
    "platform": "bluesky",
    "identifier": "yourbrand.bsky.social",
    "appPassword": "abcd-efgh-ijkl-mnop",
    "profile": "Business",
    "metadata": {
      "userId": "user_8412"
    }
  },
});
console.log(response);
{
  "id": "acc_1699f4157fb643f49d2ac447491f32a8",
  "platform": "bluesky",
  "displayName": "Your Brand",
  "handle": "yourbrand.bsky.social",
  "avatarUrl": "https://cdn.onepostly.com/avatars/yourbrand.jpg",
  "status": "active",
  "canAutoRenew": true,
  "tokenExpiresAt": "2027-03-01T12:00:00.000Z",
  "profile": "Business",
  "metadata": {
    "userId": "user_8412"
  },
  "createdAt": "2027-03-01T10:00:00.000Z",
  "updatedAt": "2027-03-01T10:00:00.000Z"
}

The response is the new account, with 201. Wrong credentials return 422 AUTH_FAILED. profile and metadata work as in a connect session. To set default post languages for the account, see Post Languages.

Connect the Same Account Again

Connecting an account that is already in the workspace does not add a second one.

The account isWhat happens
ConnectedIts access is renewed. It keeps its id, profile and metadata, and it does not count as a new account. account.connected has reconnected: true
DisconnectedIt comes back as a new connection, with the profile and metadata the new connect names. It counts as a new account. account.connected has reconnected: false

To renew one account on purpose, use Reconnect an Account. It checks that the user signs in with the same account.

List Accounts

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

const accountsApi = new AccountsApi(
  new Configuration({ accessToken: process.env.ONEPOSTLY_API_KEY })
);

const response = await accountsApi.listAccounts();
console.log(response);
{
  "data": [
    {
      "id": "acc_1699f4157fb643f49d2ac447491f3203",
      "platform": "bluesky",
      "displayName": "Your Brand",
      "handle": "yourbrand.bsky.social",
      "avatarUrl": "https://cdn.onepostly.com/avatars/yourbrand.jpg",
      "status": "active",
      "canAutoRenew": true,
      "tokenExpiresAt": "2027-03-01T12:10:00.000Z",
      "profile": "Business",
      "metadata": {
        "userId": "user_8412"
      },
      "createdAt": "2027-03-01T10:10:00.000Z",
      "updatedAt": "2027-03-01T10:10:00.000Z"
    },
    {
      "id": "acc_1699f4157fb643f49d2ac447491f3202",
      "platform": "instagram",
      "displayName": "Your Brand",
      "handle": "yourbrand",
      "avatarUrl": "https://cdn.onepostly.com/avatars/yourbrand.jpg",
      "status": "active",
      "canAutoRenew": true,
      "tokenExpiresAt": "2027-05-14T10:00:00.000Z",
      "profile": "Default",
      "metadata": {},
      "createdAt": "2027-03-01T10:05:00.000Z",
      "updatedAt": "2027-03-01T10:05:00.000Z"
    },
    {
      "id": "acc_1699f4157fb643f49d2ac447491f32a8",
      "platform": "x",
      "displayName": "Your Brand",
      "handle": "yourbrand",
      "avatarUrl": "https://cdn.onepostly.com/avatars/yourbrand.jpg",
      "status": "active",
      "canAutoRenew": true,
      "tokenExpiresAt": "2027-05-14T10:00:00.000Z",
      "profile": "Business",
      "metadata": {},
      "xSubscriptionType": "None",
      "createdAt": "2027-03-01T10:00:00.000Z",
      "updatedAt": "2027-03-01T10:00:00.000Z"
    }
  ],
  "nextCursor": null,
  "hasMore": false
}

All parameters are optional, and you can combine them.

ParameterDescription
limitAccounts per page, 1 to 100. Default 50
cursorThe nextCursor of the previous page
qSearch by name, handle or platform, in any letter case
platformOnly accounts on this platform, such as instagram
profileOnly accounts in the profile with this name, in any letter case. none lists accounts without a profile
since, untilOnly accounts connected in this window. See Time Filters
sortcreated_desc (default) for newest first, or created_asc for oldest first
includetotal adds the number of matching accounts

Pass nextCursor as cursor until hasMore is false. GET /v1/accounts/{id} reads one account. Disconnected accounts are never listed and return 404 ACCOUNT_NOT_FOUND.

Account Fields

Prop

Type

Errors

CodeHTTPCauseFix
VALIDATION_ERROR400A field is missing or malformed, such as an empty Bluesky handle or a platform that has no connect sessionCorrect the field named in param
REDIRECT_URL_REQUIRED400No redirectUrl was sent with an API key, or with headless: trueAdd redirectUrl
INVALID_REDIRECT_URL400redirectUrl is not a full URL, contains credentials, or uses http on a public hostSend a full https URL
INVALID_CONNECT_SESSION400The connectSession is malformed, was changed, or is older than 10 minutesPass it unchanged, or create a new connect session
FACEBOOK_PAGES_FAILED400Facebook did not return the user's PagesTry again, or create a new connect session
INSTAGRAM_ACCOUNTS_FAILED400Facebook did not return the linked Instagram accountsTry again, or create a new connect session
CONNECT_FAILED400The account could not be saved, or Bluesky could not sign inTry again. If it keeps happening, contact support
USAGE_BILLING_REQUIRED402The 2 free accounts are used and there is no payment methodAdd a payment method in the dashboard, then connect again
USAGE_BILLING_FAILED402Billing for the new account could not be set up, so it was not addedCheck the payment method in the dashboard, then connect again
FORBIDDEN403A read_only key, or a connect session from another workspaceUse a read_write key from the workspace that started the session
ACCOUNT_NOT_FOUND404The account does not exist or was disconnected, or the candidateId is not one of the user's Instagram accountsList the accounts or candidates again and use an id from the list
PAGE_NOT_FOUND404The candidateId is not one of the user's PagesList the candidates again and use an id from the list
AUTH_FAILED422Bluesky rejected the handle or App PasswordCheck the handle and create a new App Password
PLATFORM_UNAVAILABLE502The platform failed or did not respond, or accounts on it cannot be connected right nowTry again later. If it keeps happening, contact support
PLATFORM_MAINTENANCE503New connections to this platform are paused for nowTry again later

Errors in the browser part of the flow come back on your redirect, not as an API response. See Redirect Errors. Reconnect errors are on Account Status, and every code is in the error catalog.