Authentication
The ViralNote API uses bearer tokens. The same endpoints accept two equivalent forms — a Firebase user ID token (for first-party clients) or a long-lived API key (for backend integrations and agents).
Did your AI agent send you here?
Agents can't create accounts — this part takes a human, and it's quick: create a free ViralNote account, open Settings → API keys, create a key, and paste it back to your agent. Your agent handles the rest (MCP, skill, or REST — it already knows which).
Get an API key
- 1. Sign in to ViralNote and open Settings → API keys.
- 2. Click Create key, give it a label (e.g. "Zapier", "MCP server"), pick scopes, and optionally restrict the key to one brand workspace so a client automation cannot touch other brands.
- 3. Copy the key immediately — it's shown once and never displayed again. Store it in your secret manager / .env file.
Brand restriction
Agency accounts can lock a key to one brand workspace at create time (or later via PATCH). A restricted key only lists that brand, omits other clients from usage breakdowns, and returns 403 if a request sends a different brandId or brandId=all. Leave the restriction empty for automations that should see every workspace on the account.
Send a request
Send the key as the x-api-key header (preferred), or as a bearer token in Authorization.
# Using x-api-key header curl https://viralnote.app/api/v1/posts \ -H "x-api-key: $VIRALNOTE_API_KEY" # Using Authorization header (equivalent) curl https://viralnote.app/api/v1/posts \ -H "Authorization: Bearer $VIRALNOTE_API_KEY"
Scopes
Each key carries a set of scopes that constrain what it can do. Request only what you need.
| Scope | Grants |
|---|---|
| posts:read | List and read posts, drafts, schedules, publish results, media, accounts, and brands. Also satisfies analytics:read and comments:read, which were carved out of it. |
| posts:write | Create and edit drafts; cancel scheduled posts; manage media, brand profile, saved replies, and content generation. Scheduling and publishing require posts:publish; replying and moderating require the comments scopes. |
| posts:publish | Publish immediately, schedule, and edit content that stays queued. Requires an eligible plan. |
| comments:read | List synced comments, refresh the inbox from connected networks, and read saved replies. |
| comments:reply | Send public replies as the connected brand account, and manage saved replies and handled state. |
| comments:moderate | Hide, unhide, delete, or report comments on the connected network, and mark comments handled. Does not allow sending replies. |
| analytics:read | Read published-post metrics, per-post metric history, and audience history. Does not grant access to drafts or comments. |
| webhooks:manage | Register, update, and delete webhook subscriptions. |
| credits:read | Read the account's video-processing credit balance, estimates, transaction history, and per-brand usage breakdown. |
| credits:write | Spend credits on AI generation, and enable recurring credit spending such as Compass automation. |
Credentials created before the comment and analytics split
analytics:read and comments:read were carved out of posts:read, which already returned both. A key or token holding posts:read still satisfies them, so nothing breaks and nothing has to change.
comments:reply and comments:moderate are different: they used to be covered by posts:write plus posts:publish, and they no longer are. Nothing was granted automatically — an integration that sends replies or hides comments has to create a new API key with those scopes, or, for an OAuth connection, reauthorize and approve them. Refreshing an existing access token does not add scopes. Until then those two endpoints return 403 with a message naming the missing scope and the step that fixes it; every other endpoint is unaffected.
Rate limits
Default rate limit is 60 requests per minute per API key, with route-specific caps on heavier endpoints (e.g. /media upload at 20/min, /media/import at 10/min). A custom per-minute cap can be set on a key when you create it.
Responses that exceed the limit return 429 with a Retry-After header. Back off and retry.
Errors
All errors return a consistent JSON envelope:
{
"requestId": "abc123",
"error": {
"code": "unauthorized",
"message": "Invalid API key"
}
}Include the requestId in any support correspondence — it lets us look up the exact server-side trace.