BifrostAPI Reference Base URL  https://usebifrost.org/api

The Bifrost API

One REST API to connect creators across LinkedIn, YouTube, Instagram, TikTok, Facebook and X — and pull their profiles, posts and analytics through a single, unified schema.

Production REST · JSON 6 platforms API-key auth
📘
Prefer to explore interactively? The live, auto-generated OpenAPI explorer is at usebifrost.org/api/docs — every endpoint below is callable there.
Overview

Introduction#

Bifrost is unified creator infrastructure: connect a social account once, then read it through the same endpoints and the same response shape no matter which platform it lives on.

Instead of integrating six OAuth flows and six wildly different data models, you integrate Bifrost once. We handle each platform's OAuth, token refresh, and API quirks, and normalize everything into a consistent set of resources:

  • Accounts — a creator's connected account on one platform.
  • Profile — username, avatar, follower count, verification.
  • Posts — recent content with engagement metrics.
  • Analytics — a point-in-time snapshot of account-level metrics.

The API is organized into two surfaces. The Data API (authenticated with a project API key) is what your application calls to connect accounts and read creator data. The Management API under /v1 (authenticated with your login token) is what you use to manage organizations, projects, keys, billing and webhooks — the same operations the dashboard performs.

Getting started

Quickstart#

From zero to reading a creator's data in three steps.

1 — Create a project API key

Sign in to the dashboard, open API Keys, and create a key. It's shown once and looks like bif_live_…. Keys are scoped to a single project.

2 — Start an OAuth connection

Ask Bifrost for an authorize URL, then redirect the creator to it.

cURL
curl -X POST https://usebifrost.org/api/connect \
  -H "X-API-Key: bif_live_your_key" \
  -H "Content-Type: application/json" \
  -d '{"platform":"youtube","redirect_to":"https://yourapp.com/done"}'

# → { "authorize_url": "https://accounts.google.com/o/oauth2/...", "state": "..." }
# Redirect the creator to authorize_url. Bifrost handles the callback and
# sends them back to redirect_to?status=connected&platform=youtube&account_id=...

3 — Read their data

Once connected, use the returned account_id to fetch profile, posts, or analytics.

cURL
curl https://usebifrost.org/api/analytics?account_id=ACCOUNT_ID \
  -H "X-API-Key: bif_live_your_key"

# → { "platform":"youtube", "followers":10450, "likes":..., "views":..., "captured_at":"..." }
Getting started

Authentication#

Every request is authenticated. Which credential you use depends on the surface.

Data API — project API keys

Calls to /connect, /accounts, /profile, /posts and /analytics use a project API key (prefix bif_). Pass it either way:

HTTP headers
X-API-Key: bif_live_your_key
# — or —
Authorization: Bearer bif_live_your_key

Keys are project-scoped: a key can only ever see accounts and data belonging to its own project. The full secret is shown once at creation — store it securely. A leaked key can be revoked instantly from the dashboard or the Management API.

Management API — login token

Calls under /v1 and /auth/me use the JWT returned by /auth/login or /auth/signup:

HTTP header
Authorization: Bearer eyJhbGciOiJIUzI1NiIsIn...
🔑
Never expose an API key or login token in client-side code. API keys grant full read access to a project's connected accounts; call the API from your server.
Getting started

Base URL & versioning#

https://usebifrost.org/api

All requests are made over HTTPS to the base URL above; plain-HTTP requests are redirected to HTTPS. Request and response bodies are JSON (Content-Type: application/json).

Data-API endpoints are unversioned and stable. Management endpoints live under the /v1 prefix. New fields may be added to responses over time — treat unknown fields as forward-compatible and don't break on them.

Getting started

Rate limits & quotas#

Two limits apply to Data-API calls, both determined by your plan:

  • Per-minute rate limit — a fixed request-per-minute ceiling.
  • Monthly included calls — total API calls per calendar month.

Every authenticated response carries your remaining budget:

Response headers
X-RateLimit-Remaining: 118     # requests left in the current minute
X-Monthly-Remaining: 99872     # included calls left this month

Exceeding either limit returns 429 Too Many Requests. Slow down and retry after the window resets, or upgrade the plan for higher limits — see Plans & limits.

Getting started

Errors#

Bifrost uses conventional HTTP status codes. Errors return a JSON body with a human-readable detail:

Error response
{ "detail": "Connected-account limit reached for the Free plan. Upgrade to add more." }
StatusMeaningWhen it happens
200OKSuccessful GET / POST.
201CreatedResource created (key, project, ticket…).
204No ContentSuccessful DELETE (e.g. disconnect).
400Bad RequestMalformed input, or a redirect_to origin that isn't allow-listed.
401UnauthorizedMissing, invalid, or revoked API key / token.
402Payment RequiredPlan limit hit (accounts, projects) or a paid-only feature on Free.
403ForbiddenAuthenticated but lacking the required role.
404Not FoundResource doesn't exist or isn't in your project.
429Too Many RequestsRate limit or monthly quota exceeded.
501Not ImplementedOperation not yet supported for that platform.
502Bad GatewayUpstream platform API returned an error.
🛡️
A 404 is returned rather than 403 when you reference an account or resource outside your project — Bifrost never confirms the existence of another tenant's data.

Errors from a platform

When a live fetch (refresh=true) fails at the upstream platform, Bifrost passes through the platform's status so you can tell a permission/scope problem from a real outage. The body includes platform and provider_status:

Provider error · 403
{
  "detail": "[linkedin] GET /rest/posts -> 403: ...",
  "platform": "linkedin",
  "provider_status": 403
}

A 403 means the platform hasn't granted the required API/scope for that operation (see Platform capabilities) — not a Bifrost fault. Only genuine upstream failures return 502.

Getting started

Pagination#

List endpoints accept limit and offset query parameters and return a plain JSON array. Page by advancing offset in multiples of limit.

cURL
# second page of 50 accounts
curl "https://usebifrost.org/api/accounts?limit=50&offset=50" \
  -H "X-API-Key: bif_live_your_key"
Endpointlimit defaultmaxoffset
GET /accounts50200≥ 0
GET /posts20100≥ 0

Cache vs. live

Read endpoints (/profile, /posts, /analytics) return cached data by default and fetch live from the platform when you pass refresh=true. Every read tells you which you got via a response header:

Response header
X-Bifrost-Source: live     # fetched from the platform just now (refresh=true)
X-Bifrost-Source: cache    # served from stored data
X-Bifrost-Source: empty    # nothing cached yet — call ?refresh=true to populate

An empty cache is always a 200 with empty/null fields ([] for posts, null metrics for analytics) and source: empty — never a 404.

Data API · Connect

Connect an account#

Starts an OAuth flow and returns the platform's authorize URL. Redirect the creator there; after they consent, Bifrost exchanges the code, stores the encrypted tokens, and creates a connected account.

POST/connectAPI key

Body parameters

FieldTypeDescription
platformstringrequiredOne of linkedin, youtube, instagram, tiktok, facebook, x.
redirect_tostringoptionalWhere to send the creator after the callback. Its origin must be registered under your project's Redirect origins (Accounts page in the dashboard). Omit to receive a JSON account object instead of a redirect.
referencestringoptionalYour own id for this connection (tenant / user). Stored on the account, returned in GET /accounts, echoed on the callback as &reference=, and filterable via ?reference=. Bind it here so you never have to map by account_id after the fact.
creator_iduuidoptionalAttach to an existing Bifrost creator (a Bifrost UUID from a prior connect). To bind your own id, use reference instead.
Response · 200
{
  "authorize_url": "https://www.linkedin.com/oauth/v2/authorization?...",
  "state": "b1f0…"
}

The callback

Bifrost hosts the OAuth redirect target at GET /connect/callback — you never call it directly. When you supplied redirect_to, the creator is 302-redirected back to it with a result:

Redirect back to your app
# success (reference included when you supplied one)
https://yourapp.com/done?status=connected&platform=youtube&account_id=8f3c…&reference=tenant-42

# failure
https://yourapp.com/done?status=error&platform=youtube&message=...
🔁
Reauthorize = re-run POST /connect. Connecting the same platform account again updates it in place (refreshes tokens, keeps the same account_id and reference) — it never creates a duplicate. This is how a creator restores an expired account. Don't trust the callback query params alone for critical flows — confirm server-to-server with GET /accounts/{id}.
Data API · Accounts

List connected accounts#

Returns the connected accounts in the calling key's project.

GET/accountsAPI key

Query parameters

ParamTypeDescription
referencestringoptionalFilter to accounts bound to your external id (see reference on connect).
creator_iduuidoptionalFilter to a single creator's accounts.
limitintoptionalDefault 50, max 200.
offsetintoptionalDefault 0.
Response · 200
[
  {
    "id": "8f3c1a2e-…",
    "creator_id": "3d90…",
    "platform": "youtube",
    "username": "@creatorhandle",
    "profile_image_url": "https://…",
    "follower_count": 10450,
    "is_verified": true,
    "status": "active"
  }
]

status is one of active, expired (token lapsed and can't refresh — the creator should reconnect), or revoked.

Data API · Accounts

Get one account#

Fetch a single connected account by id — the server-to-server way to confirm a connection result (instead of trusting the callback query params).

GET/accounts/{account_id}API key
Response
200  # the account object (same shape as the list)
404  # no such account in this project
Data API · Accounts

Disconnect an account#

Permanently removes a connected account along with its cached posts and analytics, and frees a slot against your plan's connected-account limit. Fires an account.disconnected webhook.

DELETE/accounts/{account_id}API key
Response
204 No Content   # deleted
404              # no such account in this project
Data API · Read

Get profile#

Returns the stored profile snapshot for an account. Pass refresh=true to fetch live from the platform and update the snapshot.

GET/profileAPI key
ParamTypeDescription
account_iduuidrequiredThe connected account.
refreshbooloptionalRe-fetch live from the platform (default false).
Response · 200
{
  "platform": "instagram",
  "username": "@creatorhandle",
  "profile_image_url": "https://…",
  "follower_count": 10450,
  "is_verified": false
}
Data API · Read

List posts#

Returns recent posts for an account, newest first, with normalized engagement metrics.

GET/postsAPI key
ParamTypeDescription
account_iduuidrequiredThe connected account.
limitintoptionalDefault 20, max 100.
offsetintoptionalDefault 0.
refreshbooloptionalRe-fetch live from the platform.
Response · 200
[
  {
    "id": "a1…", "platform": "tiktok",
    "external_id": "73920…",
    "type": "video",
    "caption": "behind the scenes",
    "url": "https://…", "media_url": "https://…",
    "published_at": "2026-07-01T12:00:00Z",
    "likes": 2100, "comments": 88,
    "shares": 14, "views": 54200
  }
]

type is one of post, video, reel, short. Metrics not exposed by a platform are returned as null.

Data API · Read

Get analytics#

Returns the most recent account-level metrics snapshot. Pass refresh=true to capture a fresh snapshot from the platform. If none exists yet, call once with refresh=true.

GET/analyticsAPI key
ParamTypeDescription
account_iduuidrequiredThe connected account.
refreshbooloptionalCapture a fresh snapshot (default false).
Response · 200
{
  "platform": "youtube",
  "followers": 10450,
  "likes": 50120,
  "comments": 3310,
  "shares": 420,
  "views": 1200500,
  "captured_at": "2026-07-10T09:00:00Z"
}
Webhooks

Events & delivery#

Webhooks push account lifecycle events to your server as they happen, so you don't have to poll. They're available on Starter and above.

Create an endpoint from the dashboard's Webhooks page or the Management API. Each endpoint gets a signing secret (whsec_…), shown once. Bifrost delivers a JSON POST and retries on failure.

Event types

EventFires when
account.connectedA creator finishes connecting an account.
account.disconnectedAn account is disconnected / deleted.
pingA test delivery you trigger from the dashboard.

Delivery format

POST to your endpoint
X-Bifrost-Event: account.connected
X-Bifrost-Signature: sha256=9f86d0818...

{
  "event": "account.connected",
  "data": {
    "account_id": "8f3c…",
    "platform": "youtube",
    "username": "@creatorhandle",
    "platform_user_id": "UC…"
  }
}
Webhooks

Verifying signatures#

Every delivery is signed. Compute an HMAC-SHA256 of the raw request body using your endpoint secret and compare it (constant-time) to the X-Bifrost-Signature header. Reject the request if they don't match.

Node.js · Express
const crypto = require("crypto");

function verify(rawBody, header, secret) {
  const expected = "sha256=" +
    crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
  return crypto.timingSafeEqual(Buffer.from(header), Buffer.from(expected));
}

app.post("/webhooks/bifrost", express.raw({type:"*/*"}), (req, res) => {
  if (!verify(req.body, req.get("X-Bifrost-Signature"), process.env.WHSEC))
    return res.sendStatus(401);
  const evt = JSON.parse(req.body);
  // handle evt.event / evt.data …
  res.sendStatus(200);
});
Management API

Accounts & auth#

The Management API mirrors the dashboard. Authenticate with the login token (see Authentication). Signing up bootstraps an organization you own.

EndpointDescription
POST /auth/signupCreate an account + owner org. Returns a token, user, and memberships.
POST /auth/loginExchange email + password for a token.
GET /auth/meCurrent user and org memberships.
POST /auth/verify-emailConfirm an email with the token from the verification link.
POST /auth/forgot-passwordSend a reset link (always returns success; never reveals whether an email exists).
POST /auth/reset-passwordSet a new password with a reset token.
POST /auth/accept-inviteJoin an org you were invited to.
Management API

Organizations, projects & keys#

Bifrost is multi-tenant: an organization contains projects, and each project has its own API keys and connected accounts. This is how you isolate environments or customers.

EndpointDescription
POST /v1/orgsCreate an organization.
GET /v1/orgsList your organizations.
POST /v1/orgs/{id}/projectsCreate a project.
GET /v1/orgs/{id}/projectsList projects in an org.
POST /v1/projects/{id}/keysCreate an API key — the secret is returned once.
GET /v1/projects/{id}/keysList a project's keys (prefixes only).
DELETE /v1/keys/{id}Revoke a key immediately.
Management API

Team, usage, plan & webhooks#

EndpointDescription
POST /v1/orgs/{id}/inviteEmail an invitation to join the org.
GET /v1/orgs/{id}/membersList members and roles (owner / admin / member).
GET /v1/projects/{id}/usageAPI-call counts by day (?days=7).
GET /v1/orgs/{id}/planCurrent plan, limits, and live usage.
PATCH /v1/orgs/{id}/planChange plan (owners).
POST /v1/projects/{id}/webhooksRegister a webhook endpoint.
POST /v1/webhooks/{id}/testSend a ping to verify your endpoint.
DELETE /v1/webhooks/{id}Delete a webhook endpoint.
Reference

Plans & limits#

Billing is per connected account. Limits are enforced live on the Data API.

PlanPrice / moAccounts ProjectsAPI calls / moRate (req/min)Webhooks
Free$01025,00060
Starter$9505100,000120
Growth$49500201,000,000300
Scale$992,0001,0005,000,000600
EnterpriseCustomCustom volume, rate limits, SSO & audit logs, white-label, dedicated support.

See live pricing at usebifrost.org/pricing.

Reference

Data models#

Field-level reference for the core resources.

Account

FieldTypeNotes
iduuidBifrost account id — use this to read data.
creator_iduuidThe creator this account belongs to.
referencestring?Your external id, if you bound one at connect.
platformenumlinkedin·youtube·instagram·tiktok·facebook·x
usernamestring?Handle / display name.
profile_image_urlstring?Avatar URL.
follower_countint?Followers / subscribers.
is_verifiedboolPlatform verification badge.
statusenumactive·expired·revoked

Post

FieldTypeNotes
iduuidBifrost post id.
external_idstringPlatform's native post id.
typeenumpost·video·reel·short
captionstring?Text / caption.
url / media_urlstring?Permalink and media.
published_atdatetime?ISO-8601, UTC.
likes·comments·shares·viewsint?null where a platform doesn't expose the metric.

Analytics snapshot

FieldTypeNotes
followers·likes·comments·shares·viewsint?Account-level totals at capture time.
captured_atdatetime?When the snapshot was taken.
Reference

Supported platforms#

All six platforms share the same endpoints and response shapes, but what each can actually return is bounded by that platform's API and your app's review status with them. This matrix is the source of truth — read it before you rely on a field.

PlatformplatformProfilePostsAnalyticsNotes
YouTubeyoutubeFully live.
X (Twitter)x⚠️Profile & analytics live. Reading a user's timeline needs paid X API v2 access — the free tier can't, so live posts return 402 (credits-depleted) until the X app is on a paid tier.
LinkedInlinkedin⚠️Posts need LinkedIn's Community Management API (in review) — until granted, live post fetch returns 403. Member analytics don't exist in LinkedIn's member APIs (only Organization pages expose share stats) → 501. follower_count is always null for members.
TikToktiktok⚠️⚠️Follower/like counts and analytics need the user.info.stats scope + approved app (in review). Without it, profile stats are null and analytics returns 403. Posts work with video.list.
FacebookfacebookPending Meta App Review + Business Verification. Login shows "Feature Unavailable" until approved.
InstagraminstagramSame shared Meta app — pending the same review.

✅ live · ⚠️ partial / needs platform grant · ❌ unsupported by the platform's API · ⏳ pending platform review

🔌
The connection model is identical everywhere: POST /connect with the platform value, redirect the creator, then read with the returned account_id. Where a capability is gated, the failure is explicit — a provider 403/501 with platform and provider_status (see Errors), not a silent empty.