What the sandbox simulates, how to recognise sandbox responses, and the test triggers that force failures, warnings and slow publishes.
Every BetterPost project currently runs in sandbox mode. The sandbox runs the same code paths as production: request validation, per-platform rules, credit charges and refunds, scheduling, retries and webhooks. Only the last step differs: instead of calling X, LinkedIn, Instagram or Facebook, a simulated adapter returns a realistic result.
sk_test_.x-betterpost-mode: sandbox."sandbox": true.GET /v1/connect/{platform} returns a URL on BetterPost itself
(https://betterpost.dev/sandbox/authorize?state=...) instead of the network's OAuth page. The page looks like a
standard consent screen: it names your project and the platform, and lets you edit the fake account's handle
(pre-filled with @demo_brand) and display name.
profileId, if any) with a fake
platformUserId such as sbx_4829105736, then redirects to your redirectUri with
?connected=<integrationId>&platform=<platform>.redirectUri with ?error=access_denied.redirectUri, both buttons return to the dashboard's Connected accounts page.Each target is first checked against the platform's real rules (see Platforms). The simulated publish then waits a short, random time (200 to 800 ms) and returns:
platformPostId such as sbx_1849203756184930275;platformPostUrl such as https://betterpost.dev/sandbox/p/twitter/sbx_1849203756184930275, which renders a
simple preview of the post.Media URLs are validated (type, http(s) URL, alt text length) but the files are never downloaded or inspected.
Put these tokens anywhere in a post's text to force an outcome. They are case-insensitive, must be separate words,
and surrounding punctuation such as (#slow) or #fail. is ignored. Triggers are read from each target's main text (its
content override, or the shared content), so a trigger in one target's override affects only that target. X
threadItems are not scanned.
| Trigger | Effect |
|---|---|
#fail | Every target fails with Sandbox: simulated <Platform> API error (triggered by #fail). The post ends failed and every target is refunded. |
#fail:<platform> | Only that platform fails, for example #fail:instagram. #fail:x is an alias for #fail:twitter. Other targets publish, so the post ends partial. Unknown platform names are ignored. |
#warn | Targets publish but carry warningMessage: "Sandbox: simulated warning (triggered by #warn)". A post with a warning ends partial. |
#slow | Each target takes 12 seconds. The create request stops waiting after 8 seconds and answers status: "publishing"; the post finishes in the background. Use it to test polling and webhooks. |
Example: publish to X and LinkedIn where only LinkedIn fails.
curl -s -X POST https://api.betterpost.dev/v1/posts \
-H "x-access-key: $BETTERPOST_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"content": "Ship it #fail:linkedin",
"platforms": [
{ "platform": "twitter", "accountId": "acc_2x9c4v7b1n8m3k5j0h6g2f4d" },
{ "platform": "linkedin", "accountId": "acc_5l1n7k3j9h2g8f4d6s0a1q3w" }
],
"publishNow": true
}'{
"success": true,
"status": "partial",
"postId": "post_8f3k2m9q1x7v4n6b0c5d2a1z",
"platforms": [
{
"platform": "twitter",
"accountId": "acc_2x9c4v7b1n8m3k5j0h6g2f4d",
"success": true,
"platformPostUrl": "https://betterpost.dev/sandbox/p/twitter/sbx_1849203756184930275"
},
{
"platform": "linkedin",
"accountId": "acc_5l1n7k3j9h2g8f4d6s0a1q3w",
"success": false,
"error": "Sandbox: simulated LinkedIn API error (triggered by #fail:linkedin)"
}
]
}The LinkedIn credit is refunded. Note that retry republishes the stored content, so a post whose text contains a trigger fails again on retry (and is refunded again). To see a retry succeed, use the expired connection flow below.
In Dashboard → Connected accounts, the menu on an account has Simulate reauthorization required. It sets the
account's authStatus to reconnect_required and sends an account.reauthorization_required
webhook with reason: "sandbox_simulated". While an account is in that state:
400 account_reauthorization_required;Account requires reauthorization (and refund it).Run the connect flow again with the same handle and profile to reactivate it.
To exercise the full recovery path: schedule a post to the account, simulate reauthorization before it is due, let the target fail at its scheduled time, reconnect the account, then retry the post. Only the failed target is published again.
Sandbox posts are charged exactly as production posts will be (see Credits), so a Free plan's 20 monthly credits cover four X posts, or twenty LinkedIn, Instagram or Facebook posts.