Connecting accounts
You cannot publish until a social account is connected. Some platforms you can connect outright from the API; the rest need a human in a browser, and we give you a URL to send them to.
Ask what a platform needs
Never hard-code this — ask, because it varies per platform and can change:
curl "https://vm.viraldashboard.io/api/public/v1/brands/412/social-accounts/platforms" \
-H "Authorization: Bearer $VD_API_KEY"
{
"data": [
{
"platform": "telegram",
"mode": "self_connect",
"connect_method": "credentials",
"credential_fields": [
{ "name": "bot_token", "label": "Bot token", "type": "password", "required": true },
{ "name": "chat_id", "label": "Channel / chat id", "type": "text", "required": true }
]
},
{
"platform": "facebook",
"mode": "oauth",
"connect_method": "authorize_url",
"credential_fields": []
}
]
}
connect_method tells you which of the two paths below to take.
Path A — credentials (no browser)
Platforms whose connect_method is credentials connect in a single call. Post the fields
the discovery endpoint listed:
curl -X POST "https://vm.viraldashboard.io/api/public/v1/brands/412/social-accounts" \
-H "Authorization: Bearer $VD_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"platform": "telegram",
"credentials": { "bot_token": "8012345678:AAH…", "chat_id": "@acmecoffee" }
}'
A 201 means the account is connected and ready to publish to. Bad credentials come back as
422 invalid_credentials — we check them against the platform before saving, so a
201 genuinely means it works.
Path B — OAuth (a human, in a browser)
Facebook, Instagram, LinkedIn, X and the rest cannot be connected from a server: the platform demands that a human log in and consent. So you ask us for a URL, and you get that URL in front of the right person.
curl -X POST "https://vm.viraldashboard.io/api/public/v1/brands/412/social-accounts/authorize" \
-H "Authorization: Bearer $VD_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{ "platform": "facebook", "target": "page" }'
{
"data": {
"platform": "facebook",
"authorization_url": "https://…/social/connect/facebook?state=…",
"expires_in": 1800
}
}
That URL is a secret, and it is a capability. Anyone who opens it can attach their social account to your brand — no login to ViralDashboard required, which is exactly what makes it useful for onboarding a client who has no dashboard account. Treat it like a password reset link:
- Single use. The first person to complete it consumes it; a second attempt is rejected.
- Short-lived. It expires after
expires_inseconds (30 minutes). - Send it only to the intended person, over a channel you trust. Never log it, never put it in a URL you render publicly, never email it to a list.
Knowing when it worked
The person authorizes in their browser, and the flow completes without ever signing them into your dashboard.
Your side gets nothing back synchronously — the completion signal is the
account.connected webhook. Subscribe to it before you send the link:
{
"event": "account.connected",
"data": {
"brand_id": 412,
"social_account_id": 3311,
"platform": "facebook"
}
}
If you would rather not run a webhook listener, poll
GET /brands/{brand}/social-accounts and watch for the new account to appear — but the
webhook is the cheaper and faster signal.
Capacity is checked before the URL is issued
If the brand is already at its social_accounts plan cap, authorize returns
402 limit_reached and no URL is issued. That is deliberate: it would be worse to
hand someone a link, let them log in and consent, and only then tell them there was no room.
Keeping accounts alive
| Call | Does |
|---|---|
POST /social-accounts/{account}/refresh |
Refreshes the token. 409 reconnect_required means it cannot be refreshed and a human must reconnect. |
DELETE /social-accounts/{account} |
Disconnects. If the account is shared with other brands it is only detached (result: detached); if this was its last brand, the token is revoked and the account deleted (result: revoked). Fires account.disconnected. |
Watch each account's status. Only active accounts publish; expired means a
token went stale and the next publish will fail until it is refreshed or reconnected.
Mastodon and Shopify need an extra in-app step (you must supply your instance domain before
the OAuth handshake can even start), so they cannot yet be connected purely over the API — they return
422 interactive_setup_required. Connect them in the dashboard for now.