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

How to build social media API integration into your product: connect your users' accounts, upload media, publish, and read results, with working code.

Canonical page: https://www.tryadeli.com/blog/social-media-api-integration
Last updated: 2026-10-29

By Nick Reyes, Co-founder and CTO, Adeli. Published 2026-10-29.

## 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](https://www.tryadeli.com/social-media-posting-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](https://www.tryadeli.com/blog/social-media-api-review-times) 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](https://www.tryadeli.com/blog/unified-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:

```bash
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.

```javascript
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:

```javascript
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](https://www.tryadeli.com/blog/instagram-api-access-token) and [TikTok](https://www.tryadeli.com/blog/tiktok-api-access-token) access tokens, and [YouTube OAuth](https://www.tryadeli.com/blog/youtube-api-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`:

```javascript
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.

```javascript
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:

```javascript
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:

```bash
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](https://www.tryadeli.com/social-media-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](https://www.tryadeli.com/pricing), or the [vertical SaaS use case](https://www.tryadeli.com/use-cases/vertical-saas) for how teams embed this in their product.

## 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.

## Related Adeli pages

- [Social Media Posting & Scheduling API](https://www.tryadeli.com/social-media-posting-api.md): Adeli's social media posting and scheduling API publishes to Instagram, TikTok, YouTube, Facebook, X, and Bluesky in one request, now or at a set time. Free to start.
- [Social Media Analytics API](https://www.tryadeli.com/social-media-analytics-api.md): Adeli's analytics API: Instagram, TikTok, Facebook Page, and YouTube insights endpoints, plus per-post engagement on every post an account has made.
- [Social Media API for Vertical SaaS Platforms](https://www.tryadeli.com/use-cases/vertical-saas.md): Give your small-business customers posting, scheduling, analytics, inbox, and Meta ads inside your vertical SaaS, through one API.
- [Adeli Pricing — priced per profile](https://www.tryadeli.com/pricing.md): Your first 2 profiles are free, with up to 2 social accounts each. Then $6 each for profiles 3–10, $4 for 11–50, $3 for 51–100, $2 for 101–500, and $1.50 from profile 501. Every platform is included in each profile.
- [All posts on the Adeli blog](https://www.tryadeli.com/blog.md)

## About the author

Nick Reyes is the CTO behind Adeli and builds its API, its platform integrations, and the docs. He is a product engineer with 10 years of experience at Phantom, Airbnb, and Google.
