Accounts
List and connect the X/Twitter, Bluesky, LinkedIn, Threads, Mastodon, Instagram, Facebook, and Telegram accounts you post through with Chirpie.
List Connected Accounts
GET /api/v1/accounts
Authorization: Bearer chirpie_sk_YOUR_KEYResponse 200 OK
{
"data": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"platform": "x",
"platform_user_id": "123456789",
"username": "chirpie_ai",
"display_name": "Chirpie",
"avatar_url": "https://pbs.twimg.com/...",
"max_post_length": 25000,
"byo_keys": false,
"is_active": true,
"scheduled_posts": 0,
"created_at": "2026-03-01T00:00:00.000Z"
},
{
"id": "660f9500-f30c-52e5-b827-557766551111",
"platform": "bluesky",
"platform_user_id": "did:plc:abc123",
"username": "chirpie.bsky.social",
"display_name": "Chirpie",
"avatar_url": "https://cdn.bsky.app/...",
"max_post_length": 300,
"is_active": true,
"scheduled_posts": 2,
"created_at": "2026-03-15T00:00:00.000Z"
},
{
"id": "770a0600-a41d-63f6-c938-668877662222",
"platform": "linkedin",
"platform_user_id": "abc123",
"username": "John Doe",
"display_name": "John Doe",
"avatar_url": "https://media.licdn.com/...",
"max_post_length": 3000,
"account_type": "member",
"is_active": true,
"scheduled_posts": 0,
"created_at": "2026-03-20T00:00:00.000Z"
},
{
"id": "990c2800-c63f-85h8-e150-880099884444",
"platform": "linkedin",
"platform_user_id": "5515715",
"username": "Acme Robotics",
"display_name": "Acme Robotics",
"avatar_url": "https://media.licdn.com/...",
"max_post_length": 3000,
"account_type": "organization",
"is_active": true,
"scheduled_posts": 0,
"created_at": "2026-03-20T00:00:00.000Z"
},
{
"id": "880b1700-b52e-74g7-d049-779988773333",
"platform": "threads",
"platform_user_id": "12345678901",
"username": "chirpie_ai",
"display_name": "Chirpie",
"avatar_url": "https://scontent.cdninstagram.com/...",
"max_post_length": 500,
"is_active": false,
"inactive_reason": "plan_limit",
"scheduled_posts": 0,
"created_at": "2026-03-22T00:00:00.000Z"
}
],
"accounts_limit": 3,
"accounts_active": 3
}Response Envelope
| Key | Description |
|---|---|
data | The connected accounts, one object per account |
accounts_limit | The maximum number of active accounts your plan allows, or null when the plan sets no limit |
accounts_active | How many of the listed accounts are currently active |
Inactive accounts are still listed, so data can be longer than
accounts_active. Use
PATCH /api/v1/accounts/{id} to choose
which accounts are on.
Account Fields
| Field | Description |
|---|---|
id | Chirpie account UUID; use this as account_id when creating posts |
platform | "x", "bluesky", "linkedin", "threads", "mastodon", "instagram", "facebook", or "telegram" |
platform_user_id | Platform-specific user ID: X numeric ID, Bluesky DID, LinkedIn member ID, Threads user ID, Mastodon account ID, Instagram user ID, Facebook Page ID, or Telegram bot ID |
username | Username on the platform |
display_name | Display name on the platform. Accounts whose profile has no display name fall back to the handle, so this is never an empty string |
avatar_url | Profile picture URL, or null when the platform has no picture for the account |
max_post_length | Maximum post length for this account: 280 (X standard), 25000 (X Premium), 300 (Bluesky), 3000 (LinkedIn), 500 (Threads), 500 (Mastodon), 2200 (Instagram), 63206 (Facebook), or 4096 (Telegram) |
is_active | true if the account is switched on and its credentials are valid. false if it is switched off, over your plan's account limit, or its tokens expired or were revoked. This is the gate to pause an automation on: an account with is_active: false cannot publish, whatever the reason |
inactive_reason | Only present when is_active is false, and what to do about it. "plan_limit" means the account is connected but was not switched on because your plan's account limit was already reached, so activate it instead of reconnecting. "token_revoked", "token_expired" and "reauth_required" all mean the platform stopped accepting the stored credential, so the account has to be connected again. No inactive_reason at all means the account was switched off deliberately |
account_type | LinkedIn only. "member" is your own LinkedIn profile; "organization" is a LinkedIn Page you administer |
oauth_variant | Instagram only. Which route connected the account: "instagram" if you signed in with Instagram, "facebook" if you signed in with Facebook. Both publish the same way. It decides one thing, whether DELETE /api/v1/posts/{id} can take a published post down. See Connect an Instagram account |
scheduled_posts | Scheduled posts queued against this account and not yet published. Deactivating the account cancels every one of them, so check this first |
An inactive_reason of "token_revoked", "token_expired" or "reauth_required" means the account needs re-authorization, and anything it had scheduled was cancelled with its quota returned. For OAuth-based accounts (X, LinkedIn, Threads, Mastodon, Instagram, Facebook), re-authorize from the dashboard. For Bluesky, generate a new app password and reconnect. For Telegram, verify your bot token and chat ID. See When a connection dies.
Activate or Deactivate an Account
PATCH /api/v1/accounts/{id}
Authorization: Bearer chirpie_sk_YOUR_KEY
Content-Type: application/json{
"is_active": true
}| Field | Type | Required | Description |
|---|---|---|---|
is_active | boolean | Yes | true to activate the account, false to deactivate it |
Response 200 OK
Returns the updated account, in the same shape as GET /api/v1/accounts, plus
canceled_posts.
{
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"platform": "facebook",
"platform_user_id": "101234567890123",
"username": "Firecode.io",
"display_name": "Firecode.io",
"avatar_url": "https://scontent.xx.fbcdn.net/...",
"max_post_length": 63206,
"is_active": true,
"canceled_posts": 0,
"created_at": "2026-03-22T00:00:00.000Z"
}
}| Field | Description |
|---|---|
canceled_posts | Scheduled posts this call cancelled. Always 0 when activating |
Chirpie never discards a connected account when your plan runs out of room. Connecting a
platform that grants several profiles at once (Facebook, where one authorization can hand
back every Page you manage, and Instagram connected via Facebook, which brings in the
Instagram account linked to each of those Pages) stores them all and switches on as many
as your plan allows.
The rest are listed with is_active: false and inactive_reason: "plan_limit", and this
endpoint is how you choose which ones publish.
Deactivating keeps the account connected and frees a slot against your plan's account limit, so you can switch a different one on without reauthorizing.
Deactivating cancels the account's scheduled posts. A deactivated account cannot
publish, so every post still queued against it is cancelled and its monthly quota
returned to you. canceled_posts in the response says how many, and scheduled_posts on
the account tells you the number in advance. Activating the account again does not bring
them back, so schedule them afresh.
Errors
| Status | Cause |
|---|---|
400 | Body is not JSON, or is_active is missing or not a boolean |
404 | No account with that ID belongs to you |
429 | account_limit_reached: activating would exceed your plan's account limit. Deactivate another account, or upgrade. There is no Retry-After, because waiting does not free a slot |
Disconnect an Account
DELETE /api/v1/accounts/{id}
Authorization: Bearer chirpie_sk_YOUR_KEYEnds the connection. The account stops publishing immediately and stops counting against your plan's account limit, and Chirpie lets go of the credential it stored for it.
Response 200 OK
{
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"disconnected": true,
"canceled_posts": 2
}
}| Field | Description |
|---|---|
id | The account that was disconnected |
disconnected | Always true |
canceled_posts | Scheduled posts cancelled by this call, with their monthly quota returned |
Disconnecting cancels the account's scheduled posts. Every post still queued
against it is cancelled and its monthly quota returned to you. canceled_posts says
how many, and scheduled_posts on the account tells you the number in advance.
Connecting the account again does not bring them back.
Disconnect or deactivate?
Deactivate (PATCH, is_active: false) | Disconnect (DELETE) | |
|---|---|---|
| Account stops publishing | Yes | Yes |
| Frees a slot on your plan | Yes | Yes |
| Scheduled posts | Cancelled | Cancelled |
| Stored credential | Kept | Removed |
| Coming back | Activate it again, no reauthorization | Connect it again through the platform |
| Posts it already published | Kept | Kept |
Deactivate when you mean to switch the account on again, for example to make room for a different one on the same plan. Disconnect when you are done with it.
Posts a disconnected account published keep their text, status, platform ID and analytics snapshots. They stop reporting that account's handle, because the account is no longer connected.
Errors
| Status | Cause |
|---|---|
404 | No account with that ID belongs to you |
Connecting an Account
POST /api/v1/accounts checks the request in this order:
platformis validated. An unrecognised value returns400 bad_requestnaming the values you can use:Unknown platform 'twitter'. Supported: x, bluesky, linkedin, threads, mastodon, instagram, facebook, telegram.Omittingplatformstill means"x".- Platform availability. A platform that is not connectable returns
501, with codeplatform_coming_soonorplatform_unsupported. - Your plan's account limit. A new connection that would take you past
the limit returns
429with codeaccount_limit_reached.
Reconnecting never needs a free slot
If you already have at least one account on that platform, the account limit is not applied. Re-authorizing an X account whose token expired, or reconnecting an X account to move it onto your own X developer app, therefore works on every plan, including Free. Connecting a platform you have no account on still needs a free slot.
Connect Errors
| Status | Code | Cause |
|---|---|---|
400 | bad_request | platform is not a recognised value, the body is not JSON, or a platform-specific field is missing or invalid |
429 | account_limit_reached | A new connection would exceed your plan's account limit. Deactivate an account or upgrade. No Retry-After, because waiting does not free a slot |
501 | platform_coming_soon | The platform cannot be connected yet |
501 | platform_unsupported | The platform is not supported |
502 | upstream_error | The platform refused or did not answer while the connection was being started (for Mastodon, your server did not accept Chirpie's app registration). Retry after a moment |
500 | internal_error | Chirpie could not start the connection. Retry after a moment, and contact support if it keeps happening |
A failed start names the platform and never passes on the platform's raw answer:
{
"error": {
"code": "upstream_error",
"message": "Mastodon refused the connection request. Try again in a moment, and if it keeps happening contact support."
}
}Connect an X Account
POST /api/v1/accounts
Authorization: Bearer chirpie_sk_YOUR_KEY
Content-Type: application/json{
"platform": "x"
}No additional fields are required. If platform is omitted, it defaults to "x".
A platform value that is not recognised is rejected, rather than falling back
to X.
Response 200 OK
{
"data": {
"authorization_url": "https://twitter.com/i/oauth2/authorize?..."
}
}How it Works
- Call
POST /api/v1/accountswith"platform": "x" - Redirect the user to the returned
authorization_url - User authorizes Chirpie on X
- X redirects to Chirpie's callback URL
- Chirpie stores the encrypted OAuth tokens
- The account appears in
GET /api/v1/accounts
Connect a Bluesky Account
POST /api/v1/accounts
Authorization: Bearer chirpie_sk_YOUR_KEY
Content-Type: application/json{
"platform": "bluesky",
"identifier": "yourhandle.bsky.social",
"app_password": "xxxx-xxxx-xxxx-xxxx"
}| Field | Type | Required | Description |
|---|---|---|---|
platform | string | Yes | Must be "bluesky" |
identifier | string | Yes | Your Bluesky handle. The first part on its own is enough (yourhandle), and yourhandle.bsky.social, a handle on your own domain (example.com), or the email on your account all work. |
app_password | string | Yes | A Bluesky app password, shaped xxxx-xxxx-xxxx-xxxx (four groups of four lowercase letters or digits). Create one in Bluesky under Settings, Privacy and Security, App Passwords. Your account password is refused. |
Response 201 Created
Reconnecting an account you already hold returns 200 OK with the same body.
{
"data": {
"id": "660f9500-f30c-52e5-b827-557766551111",
"platform": "bluesky",
"platform_user_id": "did:plc:abc123",
"username": "yourhandle.bsky.social",
"display_name": "Your Name",
"avatar_url": "https://cdn.bsky.app/...",
"max_post_length": 300,
"is_active": true,
"created_at": "2026-03-15T00:00:00.000Z"
}
}How it Works
- Call
POST /api/v1/accountswith your Bluesky handle and app password - Chirpie checks that the password has the shape of an app password, and refuses anything else before Bluesky is asked
- Chirpie validates the credentials against the AT Protocol
- Chirpie stores the encrypted app password
- The account appears in
GET /api/v1/accounts
Create an app password in Bluesky under Settings, Privacy and Security, App Passwords. Never use your account password: Bluesky would accept it, and it gives full control of the account, so Chirpie refuses it.
Errors
A value that is not shaped like an app password answers 400 bluesky_app_password_required. The message never repeats the value you sent:
{
"error": {
"code": "bluesky_app_password_required",
"message": "Bluesky needs an app password, not your account password. Create one in Bluesky under Settings, Privacy and Security, App Passwords (https://bsky.app/settings/app-passwords). It looks like xxxx-xxxx-xxxx-xxxx."
}
}A correctly shaped app password Bluesky does not recognise (revoked, or a typo) answers 401 unauthorized with Bluesky's own reason.
Connect a LinkedIn Account
POST /api/v1/accounts
Authorization: Bearer chirpie_sk_YOUR_KEY
Content-Type: application/json{
"platform": "linkedin"
}Request Fields
| Field | Description |
|---|---|
platform | "linkedin" |
target | Optional. "profile" (the default) connects your own LinkedIn profile. "pages" connects the LinkedIn Pages you administer (Coming Soon) |
Response 200 OK
{
"data": {
"authorization_url": "https://www.linkedin.com/oauth/v2/authorization?..."
}
}How it Works
- Call
POST /api/v1/accountswith"platform": "linkedin" - Redirect the user to the returned
authorization_url - User authorizes Chirpie on LinkedIn
- LinkedIn redirects to Chirpie's callback URL
- Chirpie stores the encrypted OAuth tokens
- The connected accounts appear in
GET /api/v1/accounts
Profiles and Pages (Pages: Coming Soon)
LinkedIn has two kinds of account and they are two separate connections.
Your own LinkedIn profile is the default, and is what you get with no target
or with "target": "profile".
The LinkedIn Pages you administer are connected with "target": "pages". Each
Page becomes an account with its own id. Connecting Pages does not change or
replace your profile connection, and you can have both.
{
"platform": "linkedin",
"target": "pages"
}Each account's account_type says which it is: "member" for your profile,
"organization" for a Page. Both post exactly the same way, by passing that
account's id as account_id.
Pages count towards your plan's account limit like any other account, so a
connection that finds more Pages than your plan has room for stores the extras
switched off with inactive_reason: "plan_limit", and you choose which ones to
activate. A connection that finds no Pages you administer says so and changes
nothing.
Connecting LinkedIn Pages is coming soon. Until it lands, a request with
"target": "pages" answers 501 with
{ "error": { "code": "platform_coming_soon", "message": "LinkedIn Pages support is coming soon." } }.
Connecting your profile is unaffected.
LinkedIn uses OAuth 2.0, and the authorization flow asks you to grant Chirpie permission to post on your behalf. Tokens are refreshed automatically when they expire.
Connect a Threads Account
POST /api/v1/accounts
Authorization: Bearer chirpie_sk_YOUR_KEY
Content-Type: application/json{
"platform": "threads"
}Response 200 OK
{
"data": {
"authorization_url": "https://threads.net/oauth/authorize?..."
}
}How it Works
- Call
POST /api/v1/accountswith"platform": "threads" - Redirect the user to the returned
authorization_url - User authorizes Chirpie via Meta OAuth
- Threads redirects to Chirpie's callback URL
- Chirpie stores the encrypted OAuth tokens
- The account appears in
GET /api/v1/accounts
Threads uses Meta's OAuth 2.0 flow. You'll authorize through your Instagram/Meta account that is linked to your Threads profile. Chirpie asks for permission to read your profile, publish posts and replies, read insights on the posts it publishes, and delete the posts it created when you delete them in Chirpie. Tokens are refreshed automatically when they expire.
Connect a Mastodon Account
POST /api/v1/accounts
Authorization: Bearer chirpie_sk_YOUR_KEY
Content-Type: application/json{
"platform": "mastodon",
"instance_url": "https://mastodon.social"
}| Field | Type | Required | Description |
|---|---|---|---|
platform | string | Yes | Must be "mastodon" |
instance_url | string | Yes | Your Mastodon server, in whatever form you have it: the server (mastodon.social or https://mastodon.social/), your handle (@you@mastodon.social or you@mastodon.social), or your profile URL (https://mastodon.social/@you). Only the server is kept. Must be HTTPS. |
Response 200 OK
{
"data": {
"authorization_url": "https://mastodon.social/oauth/authorize?..."
}
}How it Works
- Call
POST /api/v1/accountswith"platform": "mastodon"and your server, handle or profile URL - Redirect the user to the returned
authorization_url - User authorizes Chirpie on their Mastodon instance
- Mastodon redirects to Chirpie's callback URL
- Chirpie stores the encrypted OAuth tokens
- The account appears in
GET /api/v1/accounts
Mastodon uses OAuth 2.0. Chirpie dynamically registers as an OAuth app on your instance. Mastodon tokens do not expire. If a token is revoked, re-authorize from the dashboard. Works with any Mastodon-compatible instance (Mastodon, Hometown, Glitch-soc, etc.).
Each connect registers Chirpie on your server afresh, so a server that is down, slow (more than 15 seconds) or refusing new apps answers 502 upstream_error naming Mastodon. Retry once the server is reachable.
A profile URL for an account on another server, such as https://mastodon.social/@you@fosstodon.org, connects you on the account's own server (fosstodon.org). A value that names no server, or one over plain HTTP, answers 400 mastodon_instance_invalid:
{
"error": {
"code": "mastodon_instance_invalid",
"message": "Enter your Mastodon server (mastodon.social), your handle (@you@mastodon.social) or your profile URL (https://mastodon.social/@you)."
}
}Connect an Instagram Account
POST /api/v1/accounts
Authorization: Bearer chirpie_sk_YOUR_KEY
Content-Type: application/json{
"platform": "instagram"
}| Field | Type | Required | Description |
|---|---|---|---|
platform | string | Yes | Must be "instagram" |
via | string | No | Which route to connect by: "instagram" (the default) signs in with Instagram, "facebook" signs in with Facebook. Any other value is refused with 400 bad_request: Unknown Instagram connect route '<value>'. Supported: instagram, facebook. |
reconnect | boolean | No | On "via": "facebook", ask again about anything the person turned down last time |
Response 200 OK
{
"data": {
"authorization_url": "https://www.instagram.com/oauth/authorize?..."
}
}Two ways to connect
| Route | via | You sign in with | Needs |
|---|---|---|---|
"instagram" (default) | An Instagram professional account (Business or Creator). No Facebook Page | ||
| Instagram via Facebook | "facebook" | The Instagram professional account linked to a Facebook Page you share |
Both publish, thread, schedule, and report analytics identically, and every
publishing option works on both. They differ in
one way: on an account connected via Facebook,
DELETE /api/v1/posts/{id} really removes the
post from Instagram, and on an account connected through Instagram it answers
501 delete_unsupported.
Each account reports its route as oauth_variant in
GET /api/v1/accounts.
Switching an account from one route to the other
Connect an account you already have through the other route and Chirpie moves
the connection it already has rather than adding a second one: the account keeps
its id and everything it has published, and its oauth_variant changes to the
route you just used. Going to "via": "facebook" adds deleting published posts;
coming back to "via": "instagram" takes it away again, and everything else is
unchanged. Where the two routes report different Instagram accounts you get a
second account instead, listed alongside the first. See
Connect an Instagram account.
How it Works
- Call
POST /api/v1/accountswith"platform": "instagram", andviaif you want the Facebook route - Redirect the user to the returned
authorization_url - The user authorizes Chirpie, on Instagram or on Facebook
- The platform redirects to Chirpie's callback URL
- Chirpie stores the encrypted OAuth tokens
- The account appears in
GET /api/v1/accounts
Several accounts from one Facebook authorization
"via": "facebook" connects the Instagram professional account linked to each
Facebook Page the user shares, so one authorization can bring in several
accounts, one row per Page. They count towards your plan's account limit like
any other account, so anything beyond the limit is stored with
is_active: false and inactive_reason: "plan_limit" rather than dropped.
Choose which ones publish with
PATCH /api/v1/accounts/{id}, or upgrade
for more account slots.
Two outcomes finish the flow with nothing connected, and each says what to do:
| What you see | Fix |
|---|---|
| No Facebook Page was shared | Start again and, in the Facebook window, choose Edit settings, then keep Show a list of the Pages you manage turned on, with every Page you want connected ticked |
| No Instagram professional account is linked to the Pages you shared | Link your Instagram account to the Page in the Instagram app (Settings, then Account type and tools), or connect Instagram on its own instead |
Instagram uses OAuth 2.0 and requires an Instagram professional account (Business or Creator). Personal accounts cannot authorize either route. Chirpie asks for permission to read your profile, publish content, and read insights on the media it publishes, plus permission to delete what it published on the Facebook route. Connections are kept alive for you: nothing has to be reconnected on a schedule. Instagram requires media, so text-only posts are not supported. Each post must include at least one image. Carousel posts support 2-10 images.
Connect a Facebook Page
POST /api/v1/accounts
Authorization: Bearer chirpie_sk_YOUR_KEY
Content-Type: application/json{
"platform": "facebook"
}Response 200 OK
{
"data": {
"authorization_url": "https://www.facebook.com/v22.0/dialog/oauth?..."
}
}How it Works
- Call
POST /api/v1/accountswith"platform": "facebook" - Redirect the user to the returned
authorization_url - The user authorizes Chirpie on Facebook and chooses which Pages to grant
- Facebook redirects to Chirpie's callback URL
- Chirpie stores a never-expiring page access token for every granted Page
- Each Page appears as its own account in
GET /api/v1/accounts
Multiple Pages and your plan limit
One Facebook authorization can grant many Pages at once. Choosing "all current and future Pages" grants every Page you manage, including Pages owned by a Business Portfolio. Chirpie imports all of them, in one account row per Page.
Pages you can post to are kept; a Page whose role does not allow creating content is skipped.
Then, if your plan includes fewer accounts than you granted Pages, Chirpie switches on as many
as it can and stores the rest with is_active: false and inactive_reason: "plan_limit".
Nothing is dropped. Choose which Pages publish with
PATCH /api/v1/accounts/{id}, or upgrade for more
account slots.
Reconnecting Facebook re-runs the import, so Pages added later are picked up without
disconnecting anything. Send "reconnect": true alongside "platform": "facebook" when you
are re-running the flow for someone who has authorized before, so Facebook asks again about
anything they turned down last time.
If a Page is missing after connecting
Facebook only hands over the Pages you tick in its authorization window, so a connect can finish with fewer Pages than you manage. Chirpie tells you which case you hit:
| What you see | Fix |
|---|---|
| No Pages were shared | Start again and, in the Facebook window, choose Edit settings, then keep Show a list of the Pages you manage turned on, with every Page you want connected ticked |
| No Pages were selected | Start again and tick every Page you want Chirpie to post to |
| Facebook granted a Page but did not return it | Check that your Facebook account has a role on the Page, or that the Page is assigned to you in Meta Business Suite, then reconnect |
| Facebook granted a Page but issued no posting token | Give your Facebook account the Content task on that Page in Meta Business Suite, then reconnect |
| None of your Pages allow posting | Ask a Page admin for the Create content task, then reconnect |
Pages already connected stay connected while you retry, and Pages you manage through a Business Portfolio are imported too, not only the ones under a direct Page role.
Facebook uses OAuth 2.0. Chirpie posts to Facebook Pages only (not personal profiles). Page access tokens are long-lived and do not expire under normal conditions. Text-only posts are supported.
Connect a Telegram Bot
POST /api/v1/accounts
Authorization: Bearer chirpie_sk_YOUR_KEY
Content-Type: application/json{
"platform": "telegram",
"bot_token": "123456789:ABCdefGHIjklMNOpqrSTUvwxYZ",
"chat_id": "-1001234567890"
}| Field | Type | Required | Description |
|---|---|---|---|
platform | string | Yes | Must be "telegram" |
bot_token | string | Yes | Bot token from @BotFather |
chat_id | string | Yes | Channel or group. A bare name is enough (channelname), and @channelname, a https://t.me/channelname link, or the numeric ID (-1001234567890) all work. |
Response 201 Created
Returns the connected account object. Reconnecting a bot and chat you already hold
returns 200 OK with the same body.
Create a bot via @BotFather on Telegram. Add the bot as an admin to your channel or group. Telegram does not use OAuth. Analytics are not available for Telegram (no metrics API).
More Platforms
Pinterest, TikTok, YouTube, and Google Business Profile are coming soon. Connecting one today answers 501 with { "error": { "code": "platform_coming_soon", "message": "<platform> support is coming soon." } }. Accounts already connected keep posting, scheduling and reporting analytics as normal.
Use Your Own X Developer App
By default, X accounts connect through Chirpie's X developer app. You can instead register your own app from developer.x.com so your X accounts post against your X API credits. Link posts from accounts connected this way are not surcharged.
See the Bring your own X keys guide for the full walkthrough, including the app settings and scopes X requires.
Set or Replace Your Keys
PUT /api/v1/accounts/x/keys
Authorization: Bearer chirpie_sk_YOUR_KEY
Content-Type: application/json{
"client_id": "YOUR_X_OAUTH2_CLIENT_ID",
"client_secret": "YOUR_X_OAUTH2_CLIENT_SECRET",
"label": "Acme social app"
}| Field | Type | Required | Description |
|---|---|---|---|
client_id | string | Yes | OAuth 2.0 Client ID from your X app |
client_secret | string | Yes | OAuth 2.0 Client Secret. Stored encrypted and never returned |
label | string | No | A name for the app, up to 80 characters |
Response 200 OK
{
"data": {
"configured": true,
"label": "Acme social app",
"client_id_last4": "3456",
"redirect_uri": "https://chirpie.ai/api/auth/x/callback",
"required_scopes": ["tweet.read", "tweet.write", "users.read", "offline.access", "media.write"],
"created_at": "2026-09-05T00:00:00.000Z",
"updated_at": "2026-09-05T00:00:00.000Z",
"message": "Saved. Add https://chirpie.ai/api/auth/x/callback as a callback URI on your X app, then reconnect your X account to switch it onto your app."
}
}Add https://chirpie.ai/api/auth/x/callback, exactly that URI, to your X app's
Callback URI / Redirect URL list. The callback always stays on Chirpie's
domain, even when the app is yours.
Setting keys does not move existing accounts. Reconnect each X account
(POST /api/v1/accounts) to switch it onto your app; the reconnected account
comes back with "byo_keys": true. Accounts you don't reconnect keep using
whichever app connected them. Reconnecting an X account you already hold does
not need a free account slot, so this works on every plan, Free included.
Check Status
GET /api/v1/accounts/x/keys
Authorization: Bearer chirpie_sk_YOUR_KEYResponse 200 OK
{
"data": {
"configured": true,
"label": "Acme social app",
"client_id_last4": "3456",
"redirect_uri": "https://chirpie.ai/api/auth/x/callback",
"created_at": "2026-09-05T00:00:00.000Z",
"updated_at": "2026-09-05T00:00:00.000Z"
}
}The client secret is never returned, only the last four characters of the client ID, so you can confirm which app is stored.
When no keys are registered, the same endpoint returns 200 OK with
configured: false rather than a 404:
{
"data": {
"configured": false,
"label": null,
"client_id_last4": null,
"redirect_uri": "https://chirpie.ai/api/auth/x/callback",
"created_at": null,
"updated_at": null
}
}Remove Your Keys
DELETE /api/v1/accounts/x/keys
Authorization: Bearer chirpie_sk_YOUR_KEYResponse 200 OK
{
"data": {
"configured": false,
"removed": true,
"message": "X developer keys removed. Reconnect any X account that was using them."
}
}Returns 404 if no keys are configured.
X accounts connected through your app stop publishing once the keys are removed, because their tokens can no longer be refreshed. Re-add the keys, or reconnect those accounts to move them back onto Chirpie's app.
Account Limits
| Plan | Max Accounts (across all platforms) |
|---|---|
| Free | 1 |
| Agent | 1 |
| Starter | 3 |
| Pro | 10 |
| Scale | 25 |
| Enterprise | Unlimited |