Skip to main content

Credential strategies

SimplePost supports several credential strategies because each interface runs in a different environment.

Strategy matrix

StrategyBest forUsed by
Environment variablesLocal development, single-account scripts, simple servicesSDK, CLI fallback, self-hosted REST server
Explicit credentials in codePer-user tokens stored by your appSDK
Scheduler connected accountsUsers who authorize accounts in the web appScheduler, MCP, scheduler-connected CLI
Local CLI secret storeTerminal-only workflows and CICLI
accounts.jsonSmall self-hosted HTTP server deploymentsSelf-hosted REST server

Environment variables

Environment variables are the fastest way to test the SDK and examples. Each platform page lists the variables it supports. Examples:

X_CLIENT_ID=
X_REFRESH_TOKEN=
TELEGRAM_BOT_TOKEN=
YOUTUBE_CLIENT_ID=
YOUTUBE_CLIENT_SECRET=
YOUTUBE_REFRESH_TOKEN=

Notes:

  • The SDK only reads env vars at the start of post() — once you have decided to use them, do not also pass options.<platform>.credentials for the same platform unless you want to override the env value.
  • Telegram has no TELEGRAM_CHAT_ID env var. The chat ID must be supplied through options.telegram.chatId on every call (or stored on a CLI / accounts.json account).
  • YouTube reads only YOUTUBE_CLIENT_ID + YOUTUBE_CLIENT_SECRET + YOUTUBE_REFRESH_TOKEN. The shorter accessToken-only variant is supported through SDK options but not through env vars.
  • Pinterest needs both PINTEREST_ACCESS_TOKEN and PINTEREST_BOARD_ID for env-only setup. Without the board ID, the SDK rejects the post.

This strategy is convenient for one account per platform. It is not ideal for multi-user products.

Explicit credentials

If your app stores per-user tokens, pass credentials through platform options:

await post({
content: { text: "Hello" },
platforms: ["x"],
options: {
x: {
credentials: {
clientId: "X_APP_CLIENT_ID",
accessToken: "USER_ACCESS_TOKEN",
refreshToken: "USER_REFRESH_TOKEN",
},
},
},
});

Persist refreshed credentials returned by the SDK when the provider rotates tokens.

Scheduler connected accounts

Scheduler stores connected account secrets encrypted in the app database. This is the right strategy when users should connect accounts themselves and when MCP or the Scheduler UI should post on their behalf.

The MCP server and scheduler-connected CLI never receive raw social platform credentials. They receive SimplePost tokens that authorize access through Scheduler.

Local CLI secret store

The CLI can store credentials directly:

simplepost setup --backend keychain
simplepost account add telegram --alias announcements --bot-token "$TELEGRAM_BOT_TOKEN" --chat-id "@channel"

Use keychain on developer machines, file-encrypted for scripts and CI, and file-plain only for local testing.

accounts.json

The self-hosted REST server reads accounts from a JSON file:

{
"accounts": [
{
"id": "x-main",
"platform": "x",
"label": "Main brand X account",
"username": "yourbrand",
"platformAccountId": "1234567890",
"credentials": {
"clientId": "...",
"refreshToken": "..."
},
"options": {
"replyToId": "1234567890"
}
}
]
}

Common fields:

FieldRequiredDescription
idYesStable account ID used in API accountIds.
platformYesOne of the supported platform keys.
labelNoHuman-readable account name.
usernameNoUsed to build post URLs when possible.
platformAccountIdVariesRequired by some providers such as Telegram and Facebook.
profilePictureNoReturned by GET /api/v1/accounts.
credentialsYesProvider-specific secret values.
optionsNoProvider defaults merged into each request.

The server validates this file at startup and refuses to boot on unknown platforms, duplicate IDs, or malformed JSON.

Token rotation

Some OAuth providers rotate refresh tokens on every refresh: the new refresh token replaces the old one, and the old one is invalidated. If you persist the wrong copy of the token, the next refresh fails and the account silently breaks.

PlatformRotates on refresh?Notes
XYes, every refreshThe SDK returns refreshed credentials in result.extraData.refreshedCredentials. Persist them before the next post.
YouTubeNoGoogle refresh tokens stay valid until the user revokes them.
Facebook / Instagram / ThreadsNo (long-lived tokens are exchanged manually)Re-issue tokens with the Access Token Debugger when they expire.
TikTokRefresh tokens last ~365 days, rotate on refreshPersist refreshed values; otherwise re-authorize.
LinkedInNo (60-day access tokens)Re-authorize when the access token expires.
PinterestRefresh tokens rotatePersist refreshed values.
Bluesky (OAuth)Yes — DPoP-bound token rotationScheduler stores DPoP keys automatically. SDK consumers must persist accessToken, refreshToken, and DPoP JWKs.
Bluesky (app password)n/aApp passwords do not expire on refresh.
Telegramn/aBot tokens do not rotate.

Where the SDK is the boundary, the SDK never writes back to your .env, accounts.json, database, or CLI store. After every post, check result.extraData?.refreshedCredentials for that platform and persist what you find:

const refreshed = results.get("x")?.extraData?.refreshedCredentials;
if (refreshed) {
await saveUserTokens(refreshed);
}

The CLI and Scheduler do persist rotated tokens automatically when posting through their stored accounts.