Skip to main content

Connect API — Build Your Own Connect Page

The Connect API lets you build the "connect your social accounts" experience inside your own product, on your own domain, with your own design — instead of sending end users to the hosted access_url page described in the White-label Integration Guide.

Your page requests a platform authorize URL from Upload-Post, redirects the end user to the social network's consent screen, and Upload-Post handles the OAuth exchange and token storage. Whether the connection succeeds, fails, or the user cancels, the user is sent back to the URL you choose with a machine-readable outcome.

When to use which:

Hosted page (access_url)Connect API (this page)
Setup effortNone — one API callYou build the UI
BrandingLogo, title, colors, language100% yours — it's your page
Domain the user seesapp.upload-post.comYours (except the OAuth hops)

Note: During the OAuth flow the user always visits the social network's own consent screen, and the network redirects briefly through app.upload-post.com (the OAuth callback registered with each platform) before returning to your redirect_url. This hop lasts milliseconds and is required by the platforms' OAuth policies — it applies to every provider in the industry.

How it works

Your backend                Your connect page             Upload-Post              Social network
│ │ │ │
│ 1. generate-jwt (API key) │ │ │
│───────────────────────────>│ │ │
│ profile JWT │ │ │
│ │ 2. POST /oauth/<platform>/start (profile JWT) │
│ │───────────────────────────>│ │
│ │ authorize_url + state │ │
│ │ 3. redirect user ──────────────────────────────────>│
│ │ │ 4. user authorizes │
│ │ │<── code + state ───────│
│ │ │ 5. token exchange, │
│ │ │ account stored │
│ │<── 6. redirect to your redirect_url ────────────────│
│ │ ?connect_status=success|error|cancelled │
  1. Your backend calls generate-jwt with your API key to obtain a profile token for the end user's profile.
  2. Your connect page calls the start endpoint below with that profile token and receives the authorize_url.
  3. You redirect the user to authorize_url (the social network's consent screen).
  4. After consent, the network redirects to Upload-Post's registered callback, which completes the exchange — authenticated by the single-use state, so the user's browser needs no session with Upload-Post.
  5. The user lands back on your redirect_url with ?connect_status=success&platform=<platform> — or, when they cancelled or something failed, with connect_status=cancelled / connect_status=error and a stable error_code (see Handling the return).

Start endpoint

POST /api/uploadposts/oauth/{platform}/start

Supported platforms: tiktok, instagram, facebook, linkedin, youtube, x (alias: twitter), threads, pinterest, google-business, snapchat

Reddit OAuth (reddit) currently returns HTTP 503 error_code: "reddit_unavailable".

Authentication

Any of:

  • Profile JWT (recommended for browser calls): Authorization: Bearer PROFILE_JWT — the token from generate-jwt. The profile is taken from the token.
  • API key (server-to-server): Authorization: Apikey YOUR_API_KEY — pass the profile in the body.

Never expose your API key in a browser; use the profile JWT there.

Request body

FieldTypeRequiredDescription
profilestringOnly with API key authProfile username the connected account will be linked to. Ignored when a profile JWT is used.
redirect_urlstringNoAbsolute http(s) URL (max 2000 chars) to send the end user back to once the attempt is over — on success, on failure and on cancel. connect_status, platform and (on failure) error_code are appended as query parameters.

Response

{
"success": true,
"platform": "instagram",
"authorize_url": "https://www.instagram.com/oauth/authorize?client_id=...&state=...",
"state": "e34zUEyZjWy6EFeI7IzGSEiUfY8O4K4_",
"expires_in": 900
}

Redirect the end user to authorize_url within 15 minutes (expires_in) — the underlying state is single-use and expires after that window. Mint a fresh one per connection attempt.

Example — browser (profile JWT)

const resp = await fetch(
'https://api.upload-post.com/api/uploadposts/oauth/instagram/start',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${profileJwt}`,
},
body: JSON.stringify({
redirect_url: 'https://yourapp.com/social/connected',
}),
}
);
const { authorize_url } = await resp.json();
window.location.href = authorize_url;

Example — server (API key)

curl -X POST "https://api.upload-post.com/api/uploadposts/oauth/tiktok/start" \
-H "Authorization: Apikey YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"profile": "end-user-123",
"redirect_url": "https://yourapp.com/social/connected"
}'

Errors

StatusMeaning
400Missing profile (API key auth) or invalid redirect_url
401Missing or invalid authentication
404Unknown platform, or profile not found for this account (error_code: PROFILE_NOT_FOUND)
500Platform client not configured on the server

Handling the return

Every attempt ends on your redirect_url (when you passed one) with the same redirect_url for all outcomes — there is no separate failure URL to configure. Upload-Post appends these query parameters:

ParameterValuesDescription
connect_statussuccess, cancelled, errorOutcome of the attempt.
platformthe platform you startedSame public names as the start endpoint (tiktok, instagram, x, …).
error_codesee belowOnly on cancelled / error. Stable and machine-readable — key your UI on this, never on the description.
error_descriptionfree text, ≤ 200 charsOnly when the social network returned one. Not guaranteed, not localized; safe to display but not to parse.

Success:

https://yourapp.com/social/connected?connect_status=success&platform=instagram

To confirm the connection server-side (recommended), call GET /api/uploadposts/users and check the profile's social_accounts.

The user cancelled on the consent screen:

https://yourapp.com/social/connected?connect_status=cancelled&platform=facebook&error_code=ACCESS_DENIED

The connection failed:

https://yourapp.com/social/connected?connect_status=error&platform=linkedin&error_code=CONNECTION_FAILED

Error codes

error_codeconnect_statusMeaningSuggested action
ACCESS_DENIEDcancelledThe end user declined or closed the network's consent screen.Offer a retry.
PROVIDER_ERRORerrorThe social network returned an OAuth error other than a cancel (outage, app misconfiguration on their side). error_description usually carries their message.Offer a retry; escalate if it persists.
ACCOUNT_ALREADY_LINKEDerrorThat social account is already connected to a different Upload-Post user. Each social account can be linked to one user at a time.Tell the user to connect a different account, or contact support to move it.
INVALID_STATEerrorThe state was missing, expired (15 min), already used, or did not match.Mint a fresh start and retry.
CONNECTION_FAILEDerrorThe token exchange or profile fetch failed on the Upload-Post side.Offer a retry; contact support if it persists.

Each attempt maps to exactly one return: the state minted at start is single-use, so a retry always goes through a fresh POST /oauth/{platform}/start.

Without a redirect_url, a failed or cancelled attempt shows the Upload-Post error screen with a human-readable message and the user can retry from your page with a freshly minted start.

Example — reading the outcome on your page

const params = new URLSearchParams(window.location.search);
const status = params.get('connect_status'); // success | cancelled | error
const platform = params.get('platform');

if (status === 'success') {
showConnected(platform);
} else if (params.get('error_code') === 'ACCESS_DENIED') {
showRetry(platform, 'You closed the authorization window.');
} else {
showRetry(platform, `Could not connect ${platform} (${params.get('error_code')}).`);
}

Security model

  • The state in the authorize URL is a 192-bit random value stored server-side, single-use and valid for 15 minutes. It is what authenticates the OAuth callback, so the end user's browser never needs an Upload-Post session.
  • The state is bound to the platform, profile and account that minted it — it cannot be replayed, reused across platforms, or combined with another account's session.
  • OAuth secrets (PKCE verifiers for TikTok and X) never leave the server; only the S256 challenge appears in the authorize URL.
  • redirect_url is validated to be an absolute http(s) URL, is never followed server-side, and is only ever reached by the end user's own browser — after a successful connection, or after a failed or cancelled one together with connect_status and error_code.

Notes per platform

  • youtube: profiles configured with custom YouTube credentials authorize against their own Google client automatically.
  • x / twitter: both names are accepted; responses always report platform: "x".
  • snapchat: connection is limited to basic profile scopes (posting requires Snapchat Public Profile API approval).
  • instagram: the end user's Instagram must be a Business or Creator account, same as the hosted flow.