An HTTP API over the accounts you connected. The same call whether it goes to Instagram, Facebook, TikTok or Bluesky: Connect handles tokens, refreshing, uploading and each platform's quirks. This is the interface HEIDI Command Center talks to, and it is open to any system of your own.
1. Get your key. You find it under Accounts, at the bottom. Every user has their own keys and sees only their own accounts. You can have several and delete one without touching the others.
2. Send it in the header X-Heidi-Key on every call.
3. The base URL is https://heidiconnect.app. Everything answers JSON, always UTF-8.
curl https://heidiconnect.app/api/me \
-H "X-Heidi-Key: $HEIDI_KEY"
{"user": {"id": "u_...", "email": "[email protected]"},
"accounts": 4,
"base_url": "https://heidiconnect.app"}
The key can also be sent as ?key= in the URL, but do not do that in
production: URLs end up in logs, browser history and proxy servers. The header does not.
instagram, facebook, tiktok, youtube, bluesky.
Every account has id, username, capabilities and
token_expires_in_days. The tokens themselves never leave the server.profile fetched from the platform.
If the profile fetch fails you get profile_error instead, and the rest of the response stands.{"deleted": true}.queued, processing,
published or failed, with the platform's id and URL once it is done.POST /api/post
Content-Type: application/json
X-Heidi-Key: $HEIDI_KEY
{
"account_id": "instagram_c176b820",
"kind": "auto",
"caption": "Ny låt ute nu.",
"media": ["https://exempel.se/omslag.jpg"],
"options": {},
"wait": true
}
kind: auto (recommended), text, image,
video, reel, story or carousel.
On auto the type is chosen from what you send: one image becomes an image, one video
becomes video or reel, several files become a carousel, nothing becomes text.
media: https URLs or paths to files you uploaded. Several files in the list become a carousel. The platforms fetch the file themselves, so the URL has to stay open for as long as the publish is running.
wait: true holds the call open until the platform has finished and you
get status: "published" right away. false answers immediately with
202 and a post_id you can ask about whenever you want. If it takes too
long with wait: true you still get 202 and a hint telling
you where to fetch the status. Nothing is lost.
options: the platform specific things that fit nowhere else, for example
TikTok's privacy level or whether comments should be on. An empty object is almost always enough.
Instagram: first_comment posts that text as the first comment on the post
(hashtags, for a clean caption); accounts without the comments permission get the text
appended to the caption instead. thumb_offset (ms) picks the cover frame.
400 if
the account is not TikTok.{"account_id", "comment_id", "text"}.{"account_id", "recipient_id", "text"}.The code is in place, but the platforms do not let us in yet. Comments and direct messages on Instagram and Facebook require permissions we apply for in a second round with Meta. Until then these calls answer with an error from the platform, not with empty lists, so you never have to guess whether it is quiet or broken.
| Platform | Publish | Read and reply |
|---|---|---|
| image, video, reel, story, carousel | statistics, posts, comments, reply, DM, send DM | |
| text, image, video, reel, story, carousel | statistics, posts, comments, reply, DM, send DM | |
| TikTok | image, video, carousel | statistics, posts |
| YouTube | video | statistics, posts |
| Bluesky | text, image, video, carousel | statistics, posts, comments, reply, DM, send DM |
| TikTok Business | image, video, carousel | statistics, posts, comments, reply, like comments |
The table shows what Connect can do against each platform. What the platform lets through right now is a different matter, decided by the review status at Meta and TikTok.
Errors always come as JSON in the same shape, and the HTTP code says whose fault it is:
{"error": "Kontot instagram_x finns inte", "details": null}
401: the key is missing or wrong. 404: the account or the post does not exist,
or belongs to someone else. 400: bad body, and details points out
exactly which field is off. 502: the platform answered with an error, and
error is the platform's own message in plain text.
wait: true gives up after 600 seconds and hands over to post_id.POST /api/post answers 429 with details.kvot and the date the quota resets.
GET /api/me shows kvot: anvant (used), tak (cap), kvar (left), aterstalls (reset date). Command Center seats are not capped.Every call only reaches accounts that belong to the key's user. There is no way
to reach someone else's accounts with your own key, not even with the right account_id:
ownership is checked in the database query, not afterwards.
Delete it under Accounts and create a new one. The old one stops working immediately, and your connected accounts are untouched, so you do not have to reconnect anything. If you run several systems it is worth giving each system its own key: then you can pull one out without shutting down the rest.