Social Media API Integration: A Step-by-Step Build

By Nick ReyesPublished 6 min read

The short answer

To build social media API integration into a product, model each customer as a profile, let them connect their accounts through OAuth, store and refresh their tokens, upload media in each platform's format, publish, then track each platform's status and read analytics. Going direct, you repeat that per network after each platform's app review. With Adeli, one API covers Instagram, Facebook, TikTok, YouTube, X, Bluesky and LinkedIn.

A social media API integration in a product has six parts: customer records, account connection over OAuth, token storage and refresh, media upload, publishing with per-platform status, and analytics. This guide walks through each part for a product where your customers connect their own accounts. The code uses Adeli's API, and each step says what you'd build yourself if you went direct.

What do you need before you start?

  • An Adeli API key. Sign in at app.tryadeli.com and create one under Settings → API keys.
  • A registered OAuth callback. Add your app's return URL under Settings → OAuth. It must match the redirectUrl you send exactly.
  • A test account on one network, such as an Instagram professional (Business or Creator) account you don't mind posting to. There's no sandbox mode.
  • Server-side code. Your API key never goes to a browser.

Going direct instead, you'd also need a developer app on every network, business verification on Meta, and an approved review on each platform before your first real user connects. Our review times by platform lists each one.

Step 1: Pick your networks and what you need on each

Start from what your users need to do, then check which networks support it. Publishing is the common case. Analytics and comments are the next two features most products add.

Network Publish Analytics Comments
Instagram Posts, Reels, Stories, carousels Yes Yes
Facebook Pages Posts, photos, video Yes Yes
TikTok Video, photo posts Yes Yes
YouTube Video uploads Yes Yes
LinkedIn Text and image posts, Pages and profiles Yes Not yet
X Posts with media No No
Bluesky Posts with media No No

Through Adeli these all sit behind one API. Going direct, each row is its own integration.

Step 2: Decide whether to integrate directly or use a unified API

Use a unified social media API unless social media is your core product. Going direct has no per-account fee, but your team builds and maintains every network's app, review, OAuth flow and media rules. A unified API like Adeli has already done the reviews, so you ship on day one.

The usual objection is that per-post fees add up at scale. Adeli doesn't charge per post, so the bill follows your customer count, not their posting volume. X is the exception, because X charges for its own API. The full comparison is in unified social media API vs. direct integration.

Step 3: Create a profile for each of your customers

A profile is one of your customers. It owns the accounts that customer connects. Pass your own customer ID as externalId so you don't have to store a mapping, and an Idempotency-Key so a retry doesn't create a duplicate:

curl -X POST "https://app.tryadeli.com/api/v1/profiles" \
  -H "Authorization: Bearer $ADELI_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: customer-123" \
  -d '{"name":"Client A","externalId":"customer-123"}'

Save the returned id. Every later call names a profile, either by profileId or by an accountId that belongs to it.

Step 4: Let your customer connect their accounts

Start a connection on your server, send your customer's browser to the returned authUrl, then poll the session until it finishes. Your customer signs in on the platform's own page, and you never handle their password or the platform token.

const adeli = (path, init) =>
  fetch(`https://app.tryadeli.com/api/v1${path}`, {
    ...init,
    headers: {
      Authorization: `Bearer ${process.env.ADELI_API_KEY}`,
      "Content-Type": "application/json",
      ...init?.headers,
    },
  }).then((response) => response.json());
 
const session = await adeli(`/profiles/${profileId}/connect`, {
  method: "POST",
  body: JSON.stringify({
    platform: "tiktok",
    redirectUrl: "https://yourapp.example/callback",
  }),
});
 
// Redirect the customer's browser here. Don't fetch it from your server.
return Response.redirect(session.authUrl);

When they finish, Adeli sends the browser back to your redirectUrl with a connectionSessionId. Treat that as a hint and confirm on the server:

const TERMINAL = new Set(["connected", "failed", "expired"]);
let state = await adeli(`/profiles/${profileId}/connect/${sessionId}`);
while (!TERMINAL.has(state.status)) {
  await new Promise((resolve) => setTimeout(resolve, 2000));
  state = await adeli(`/profiles/${profileId}/connect/${sessionId}`);
}
if (state.status !== "connected") throw new Error(state.error?.code ?? state.status);
const accountId = state.accountId;

The platform value is the only thing that changes per network: instagram, facebook, tiktok, youtube, x, bluesky or linkedin. Sessions expire after ten minutes.

Step 5: Keep tokens fresh

Every platform's access token expires on its own schedule, and an expired token means a failed post. Going direct, you store each token encrypted and refresh it before it lapses:

Platform Access token lifetime What you refresh with
Instagram (Instagram Login) 60 days, long-lived The long-lived token itself, before it expires
TikTok 24 hours A refresh token that lasts 365 days
YouTube About 1 hour A Google refresh token

Sources: our guides to Instagram and TikTok access tokens, and YouTube OAuth.

With Adeli, you skip this step. Adeli stores the tokens and refreshes them. Each account reports a connectionStatus of connected, expired or reconnect_required. Check it when you list accounts, and send the customer back through Step 4 when it isn't connected:

const { accounts } = await adeli(`/accounts?profileId=${profileId}`);
const needsReconnect = accounts.filter((a) => a.connectionStatus !== "connected");

Step 6: Upload media once

Ask Adeli for a short-lived upload URL, PUT the file straight to it, and publish with the returned upload_id. Large files never pass through your server or Adeli's API servers.

const { uploads } = await adeli("/media/uploads", {
  method: "POST",
  body: JSON.stringify({
    profileId,
    platform: "tiktok",
    files: [{ clientId: "video", purpose: "post_media", contentType: "video/mp4", size: file.size, filename: "launch.mp4" }],
  }),
});
 
const [upload] = uploads;
await fetch(upload.url, { method: upload.method, headers: upload.headers, body: file });

Send every returned header unchanged, and don't send your API key to the upload URL. The URL expires after ten minutes.

Adeli then checks the file's real format, whatever its name says, and handles each platform's rules: images converted to JPEG for TikTok photo posts and resized for Bluesky, video sent through YouTube's resumable upload or Instagram's container processing. Going direct, each of those is code you write per platform.

Step 7: Publish and track each platform's status

Publish with one POST /api/v1/posts per account, then follow the status the response gives you. Some platforms publish right away and return 201. TikTok and YouTube accept the post and process it, so they return 202 with a status URL:

const post = await adeli("/posts", {
  method: "POST",
  body: JSON.stringify({
    platform: "tiktok",
    accountId,
    tiktok_title: "Launch day #buildinpublic",
    privacy_level: "PUBLIC_TO_EVERYONE",
    music_usage_confirmed: true,
    video: { upload_id: upload.uploadId },
  }),
});
 
// 202: TikTok is processing. Poll until a final status.
const status = await adeli(
  `/posts/tiktok/status?accountId=${accountId}&publishId=${encodeURIComponent(post.publishId)}`,
);

TikTok's status ends at PUBLISH_COMPLETE, SEND_TO_USER_INBOX or FAILED, and a failure carries TikTok's own failureReason, such as spam_risk_too_many_posts. Show that reason to your customer instead of a generic error.

Don't retry a publish blindly. A timeout doesn't mean the post failed, and a retry can publish it twice. Check the status endpoint or list the account's posts first.

Step 8: Read analytics and comments

Analytics and comments are reads on the same connected account. For example, Instagram account insights and the newest post's comments:

curl "https://app.tryadeli.com/api/v1/analytics/instagram/account-insights?accountId=$ACCOUNT_ID" \
  -H "Authorization: Bearer $ADELI_API_KEY"
 
curl "https://app.tryadeli.com/api/v1/comments?platform=instagram&accountId=$ACCOUNT_ID" \
  -H "Authorization: Bearer $ADELI_API_KEY"

Reads that span several accounts can come back 207 with status: "partial": the data from the platforms that answered, plus an errors entry for the one that didn't. Check status, not only response.ok. The analytics API lists the metrics per network.

How do you know it worked?

A working integration connects an account, publishes, and shows a final status your customer can read. The errors you'll see most often, and what to do:

Error What it means Fix
400 invalid_redirect_uri redirectUrl isn't registered Add the exact URI under Settings → OAuth
422 connection_expired The customer's authorization lapsed Send them through the connect flow again
403 permission_required The account lacks a permission or Page role Reconnect after the customer restores access
422 invalid_media The file doesn't match its type or exceeds a limit Check the platform's media limits
429 rate_limited The platform is throttling Retry after the returned delay
402 x_billing_required X needs a card on file Add one; X's API fees are pay-per-use

Every publish returns its own status per platform, so one network failing never blocks the others. Adeli also logs every call it makes to a platform, with the status, latency and the platform's error code. If a post fails and the reason isn't clear, a person at hello@tryadeli.com can trace exactly what the platform said.

Is there a faster way to do this?

A unified API is the faster way, because you build these parts once instead of once per network. Going direct, Steps 4 to 7 repeat for every network, after each platform's developer app and review. Adeli holds the developer apps, has passed each platform's review, refreshes the tokens and ships platform API changes, so none of that work lands on your team.

Adeli is free to start, with no per-post fees. See pricing, or the vertical SaaS use case for how teams embed this in their product.

faq

Frequently asked questions

How long does a social media API integration take to build?

With a unified API like Adeli, a first account can connect and publish the same day. Going direct, the code takes weeks per network, and platform review adds more: about a week per Meta App Review round, and often months for LinkedIn's partner review and YouTube's quota audit.

What's the hardest part of integrating social media APIs?

The publish call is the easy part. The hard parts are app review on each platform, a different OAuth flow and token lifetime per network, each platform's media rules, and keeping up with API changes after launch.

Do my users need to give me their social media passwords?

No. Every major platform uses OAuth: your user signs in on the platform's own page and approves the permissions. Your product, or a unified API like Adeli, only ever receives a token.

Should I build social media integrations myself or use a unified API?

Build directly if social media is your core product and you have engineers to own each network long term. Use a unified API if social media is one feature of your product and you want to ship this quarter without going through every platform's review.

Power your next project
with one ads & social media API

Give your users publishing, scheduling, and analytics through one integration your team can actually maintain.

Try with agents

Start with free credits. No credit card required.