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.
| Method | Platforms | Where the user signs in |
|---|---|---|
| Dashboard | All | The Connections page in Onepostly |
| Connect session | All except Bluesky | The platform's sign-in, then back to your app |
| App Password | Bluesky | Nowhere. 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"
}| Field | Description |
|---|---|
platform | Required. Any platform id except bluesky |
redirectUrl | Required with an API key. The https URL the browser returns to. http works only on localhost |
profile | The profile for the account, created if missing. Defaults to Default |
metadata | Your 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 |
loginMethod | Instagram only. instagram_login (default) or facebook_login |
headless | true 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.
| Parameter | Description |
|---|---|
connected | The platform, such as x or instagram |
accountId | The account id. Use it as accountId when you publish |
handle | The account handle without a leading @, when the platform has one |
reconnected | 1 when the session came from Reconnect Account |
https://yourapp.com/oauth/onepostly?connected=x&accountId=acc_1699f4157fb643f49d2ac447491f32a8&handle=yourbrandYour 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.
Link Accounts to Your Users
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.
- Create the session with
metadata, such as{ "userId": "user_8412" }. - When the redirect or
account.connectedarrives, save the accountidwith that user.
| Keep in mind | Why |
|---|---|
| Handle both the redirect and the webhook | The 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 account | Both get the first user's metadata |
| Check the account is the user's | Your API key can publish to every account |
Clean up on account.disconnected | It still carries the erased metadata |
Keep secrets out of metadata | Responses 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 send | Result |
|---|---|
"key": "value" | Sets the key |
"key": null | Removes the key |
| A key you leave out | Keeps its value |
"metadata": null | Removes 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.
| Parameter | Description |
|---|---|
error | A stable code from the table below. Build your logic on it |
error_description | A 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.| Code | Cause | What to do |
|---|---|---|
ACCESS_DENIED | The user declined or cancelled | Let the user start again |
PLATFORM_ERROR | The platform returned an error | Show error_description, then retry |
SESSION_EXPIRED | The sign-in finished after expiresAt | Create a new session |
INVALID_CALLBACK | No sign-in code, or a changed session | Create a new session |
SESSION_MISMATCH | The Onepostly user or workspace changed | Sign in to the right workspace and retry |
FORBIDDEN | The Onepostly user is not an owner or admin | Ask an owner or admin |
PLATFORM_UNAVAILABLE | The platform cannot be connected right now | Try again later |
RECONNECT_MISMATCH | A reconnect signed in with another account | Sign in with the original account |
NO_ACCOUNTS | No Page or linked account to connect | Set one up on the platform, then retry |
USAGE_BILLING_REQUIRED | No payment method past the 2 free accounts | Add a payment method in your dashboard |
USAGE_BILLING_FAILED | Billing for the account could not be set up | Check your payment method in the dashboard |
AUTHORIZATION_FAILED | The platform did not finish the sign-in | Try again. If it persists, contact support |
CONNECT_FAILED | The account could not be connected | Try 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.
| Parameter | Description |
|---|---|
platform | facebook or instagram |
step | select_page for Facebook, select_instagram_account for Instagram |
connectSession | The 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 is | What happens |
|---|---|
| Connected | Its access is renewed. It keeps its id, profile and metadata, and it does not count as a new account. account.connected has reconnected: true |
| Disconnected | It 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.
| Parameter | Description |
|---|---|
limit | Accounts per page, 1 to 100. Default 50 |
cursor | The nextCursor of the previous page |
q | Search by name, handle or platform, in any letter case |
platform | Only accounts on this platform, such as instagram |
profile | Only accounts in the profile with this name, in any letter case. none lists accounts without a profile |
since, until | Only accounts connected in this window. See Time Filters |
sort | created_desc (default) for newest first, or created_asc for oldest first |
include | total 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
| Code | HTTP | Cause | Fix |
|---|---|---|---|
VALIDATION_ERROR | 400 | A field is missing or malformed, such as an empty Bluesky handle or a platform that has no connect session | Correct the field named in param |
REDIRECT_URL_REQUIRED | 400 | No redirectUrl was sent with an API key, or with headless: true | Add redirectUrl |
INVALID_REDIRECT_URL | 400 | redirectUrl is not a full URL, contains credentials, or uses http on a public host | Send a full https URL |
INVALID_CONNECT_SESSION | 400 | The connectSession is malformed, was changed, or is older than 10 minutes | Pass it unchanged, or create a new connect session |
FACEBOOK_PAGES_FAILED | 400 | Facebook did not return the user's Pages | Try again, or create a new connect session |
INSTAGRAM_ACCOUNTS_FAILED | 400 | Facebook did not return the linked Instagram accounts | Try again, or create a new connect session |
CONNECT_FAILED | 400 | The account could not be saved, or Bluesky could not sign in | Try again. If it keeps happening, contact support |
USAGE_BILLING_REQUIRED | 402 | The 2 free accounts are used and there is no payment method | Add a payment method in the dashboard, then connect again |
USAGE_BILLING_FAILED | 402 | Billing for the new account could not be set up, so it was not added | Check the payment method in the dashboard, then connect again |
FORBIDDEN | 403 | A read_only key, or a connect session from another workspace | Use a read_write key from the workspace that started the session |
ACCOUNT_NOT_FOUND | 404 | The account does not exist or was disconnected, or the candidateId is not one of the user's Instagram accounts | List the accounts or candidates again and use an id from the list |
PAGE_NOT_FOUND | 404 | The candidateId is not one of the user's Pages | List the candidates again and use an id from the list |
AUTH_FAILED | 422 | Bluesky rejected the handle or App Password | Check the handle and create a new App Password |
PLATFORM_UNAVAILABLE | 502 | The platform failed or did not respond, or accounts on it cannot be connected right now | Try again later. If it keeps happening, contact support |
PLATFORM_MAINTENANCE | 503 | New connections to this platform are paused for now | Try 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.