Quickstart
From an empty terminal to a post published on a real social account. Five calls, about ten minutes.
You will need an account with at least one connected social account and a plan that includes API access. If you have no account connected yet, do Connecting accounts first — you can do that over the API too.
1. Mint a key
In the dashboard go to Settings → API & Webhooks → API Keys and create a key.
Give it the scopes this guide uses: brands:read, accounts:read,
posts:read and posts:write.
The secret is shown once. When you close that dialog it is gone — we store only a hash. Copy it somewhere safe now. If you lose it, delete the key and mint another.
Keep it in your environment, not in your code:
export VD_API_KEY="vd_live_…"
export VD_API="https://vm.viraldashboard.io/api/public/v1"
A key beginning vd_test_ is a test key: it can read everything but every write
returns 403 test_mode_write_forbidden. To publish, you need a vd_live_ key.
2. Find your brand
Everything in the API hangs off a brand, so start by listing the brands your key can reach.
curl "$VD_API/brands" \
-H "Authorization: Bearer $VD_API_KEY"
{
"data": [
{
"id": 412,
"name": "Acme Coffee",
"slug": "acme-coffee",
"timezone": "Europe/London"
}
]
}
Take the id — 412 here. Every path below is nested under it.
You will only ever see brands your key's user is a member of. Asking for someone else's brand returns
403 brand_forbidden, not an empty list — the API tells you that you were denied rather
than pretending the brand does not exist.
3. Pick an account to publish to
curl "$VD_API/brands/412/social-accounts" \
-H "Authorization: Bearer $VD_API_KEY"
{
"data": [
{
"id": 3310,
"platform": "telegram",
"name": "testtelegramo",
"status": "active"
}
]
}
Publish only to accounts whose status is active. An expired account needs
reconnecting before it will accept a post.
4. Publish a post
This is a write, so it needs an Idempotency-Key: any unique string you generate. It makes the call
safe to retry — if the response never reaches you, sending the identical request again returns the original
result instead of publishing twice.
curl
curl -X POST "$VD_API/brands/412/posts" \
-H "Authorization: Bearer $VD_API_KEY" \
-H "Idempotency-Key: $(uuidgen)" \
-H "Content-Type: application/json" \
-d '{
"content": "Hello from the ViralDashboard API 🚀",
"schedule_type": "now",
"accounts": [3310]
}'
Node
import { randomUUID } from 'node:crypto'
const res = await fetch(`${process.env.VD_API}/brands/412/posts`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.VD_API_KEY}`,
'Idempotency-Key': randomUUID(),
'Content-Type': 'application/json',
},
body: JSON.stringify({
content: 'Hello from the ViralDashboard API 🚀',
schedule_type: 'now',
accounts: [3310],
}),
})
if (!res.ok) {
const { error } = await res.json()
throw new Error(`${error.code}: ${error.message}`)
}
const { data: post } = await res.json()
console.log(post.id, post.status)
PHP
use Illuminate\Support\Facades\Http;
$response = Http::withToken(env('VD_API_KEY'))
->withHeaders(['Idempotency-Key' => (string) Str::uuid()])
->post(env('VD_API').'/brands/412/posts', [
'content' => 'Hello from the ViralDashboard API 🚀',
'schedule_type' => 'now',
'accounts' => [3310],
]);
if ($response->failed()) {
$error = $response->json('error');
throw new RuntimeException("{$error['code']}: {$error['message']}");
}
$post = $response->json('data');
You get back 201 and the post, including the per-account variant it just created:
{
"data": {
"id": 1000241,
"content": "Hello from the ViralDashboard API 🚀",
"status": "publishing",
"platforms": ["telegram"],
"variants": [
{
"id": 5521,
"social_account_id": 3310,
"platform": "telegram",
"account_name": "testtelegramo",
"status": "pending"
}
]
}
}
5. Watch it land
Publishing is asynchronous: the 201 means we accepted the post, not that the network has accepted it.
Poll the lightweight status endpoint until it settles.
curl "$VD_API/brands/412/posts/1000241/status" \
-H "Authorization: Bearer $VD_API_KEY"
{
"data": {
"id": 1000241,
"status": "published",
"scheduled_at": null,
"published_at": "2026-07-12T09:00:04+00:00"
}
}
status goes publishing → published, or failed if the
network rejected it. Fetch the full post to see each variant's outcome and its
platform_post_url.
Polling is fine for one post. For anything at volume, subscribe to the post.published and
post.failed webhooks and let us call you instead — see
Webhooks.
Where to go next
- Errors — every code you can get back, and what to do about each.
- Idempotency — retry writes without publishing twice.
- Webhooks — stop polling.
- API reference — the other fifty endpoints.