Authentication
One Bearer key, a set of scopes, and a brand you are allowed to touch. Everything else follows from those three.
The key
Mint keys in Settings → API & Webhooks → API Keys. Send yours as a Bearer token on every request:
curl "https://vm.viraldashboard.io/api/public/v1/brands" \
-H "Authorization: Bearer vd_live_…"
Keys look like vd_live_… or vd_test_… followed by 40 characters.
The secret is displayed exactly once, at creation. We store only a hash, so we cannot show it to you again and we cannot recover it for you. Lost a key? Delete it and mint a new one — deletion takes effect immediately.
Treat a key like a password: server-side only. It carries the permissions of the user who created it, so a key embedded in a browser or a mobile app hands those permissions to anyone who opens the developer tools.
Live keys and test keys
| Prefix | Reads | Writes |
|---|---|---|
vd_live_ | Yes | Yes |
vd_test_ | Yes | No — 403 test_mode_write_forbidden |
A test key is for wiring up and exploring against your real data without any risk of publishing. It is not a sandbox with fake data: it reads the same brands and posts a live key would, it simply refuses to change anything.
Scopes
Each key carries an explicit list of scopes, and every endpoint names the one it needs. Calling an endpoint whose
scope you did not grant returns 403 insufficient_scope — even if the key's user could do it
in the dashboard.
Scopes come in read/write pairs per resource:
| Scope | Covers |
|---|---|
brands:read / brands:write | Listing, reading and creating brands |
accounts:read / accounts:write | Social accounts: list, connect, refresh, disconnect |
posts:read / posts:write | Posts: list, read, create, update, delete, submit |
media:read / media:write | Media library: list, upload, import from URL |
analytics:read | Post and account analytics |
linkinbio:read / linkinbio:write | Link-in-Bio pages |
content:read / content:write | Content plans and scheduling plan items |
feeds:read / feeds:write | RSS feeds and articles |
webhooks:read / webhooks:write | Webhook endpoints, secrets and deliveries |
ai:write | AI captions, hashtags and images (consumes credits) |
reports:read / reports:write | Reports, exports and schedules |
Grant the narrowest set that does the job. A key that only publishes needs posts:write and
accounts:read — not webhooks:write, and certainly not ai:write,
which can spend credits.
Brands are authorized, not filtered
Almost every path is nested under a brand: /brands/{brand}/posts. The key's user must be a
member of that brand. If they are not, you get 403 brand_forbidden.
Note the deliberate choice: a brand you cannot reach is a 403, not an empty 200.
Silently returning nothing would let a bug in your code look like "that brand has no posts". The API would
rather tell you that you were denied.
What each failure means
| Status | Code | Meaning |
|---|---|---|
401 | unauthorized | No key, malformed key, or a key that has been deleted. |
403 | insufficient_scope | Valid key, but it lacks the scope this endpoint needs. |
403 | brand_forbidden | Valid key, but its user is not a member of that brand. |
403 | test_mode_write_forbidden | A vd_test_ key attempted a write. |
A 401 means fix the key; a 403 means the key is fine, the permission is not.
Never retry either — nothing about them is transient. See
Errors for the full catalog.