When your clients own the social accounts, they should connect them themselves: they sign in on each network, and you never handle their passwords. Reelspan gives you two ways to do that without a Reelspan login for them:
- A hosted connect link: one call returns a link to a page with your logo, texts and language. Send it to your client.
- Your own connect page: you build the page, and call the Connect API to start each network's sign-in.
A hosted connect link
curl -X POST https://api.reelspan.com/v1/connect-links \
-H "Authorization: Bearer $REELSPAN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"username": "my-brand",
"platforms": ["tiktok", "instagram", "youtube"],
"logo_image": "https://example.com/logo.png",
"connect_title": "Connect your accounts to Example Studio",
"redirect_url": "https://example.com/onboarding/done",
"redirect_button_text": "Back to Example Studio",
"language": "es",
"connect_theme": "dark"
}'import { Reelspan } from 'reelspan';
const reelspan = new Reelspan();
const { access_url } = await reelspan.connect.link('my-brand', {
platforms: ['tiktok', 'instagram', 'youtube'],
logo_image: 'https://example.com/logo.png',
connect_title: 'Connect your accounts to Example Studio',
redirect_url: 'https://example.com/onboarding/done',
redirect_button_text: 'Back to Example Studio',
language: 'es',
connect_theme: 'dark',
});from reelspan import Reelspan
reelspan = Reelspan()
access_url = reelspan.connect.link(
"my-brand",
platforms=["tiktok", "instagram", "youtube"],
logo_image="https://example.com/logo.png",
connect_title="Connect your accounts to Example Studio",
redirect_url="https://example.com/onboarding/done",
redirect_button_text="Back to Example Studio",
language="es",
connect_theme="dark",
)["access_url"]{ "success": true, "access_url": "https://app.reelspan.com/connect?token=eyJhbGciOiJIUzI1NiIs…", "duration": "48h" }Send access_url to your client. It works for 48 hours; make a new one whenever you need it. Anyone with the link can connect accounts to that one profile, so send it only to the person it is for.
What the page shows
- Your logo, title and description, or Reelspan's default texts.
- Each network you listed in
platforms(every network when you leave it out). OAuth networks have a Connect button that opens the network's sign-in; Bluesky and the other credentials networks show a short form for the token or key. - For each connected network: the account's name, a Disconnect button, and Reconnect when the account needs it.
- The profile's scheduled posts, unless you send
show_calendar: false. - A button back to your
redirect_url, labelled withredirect_button_text(default "Done").
After each sign-in your client returns to the same page, which says whether the network connected, was cancelled, or failed and why.
Branding options
| Field | What it does | Plans |
|---|---|---|
username |
The profile the link connects accounts to. Required. | All |
platforms |
The networks to offer, by id (up to 20). Your client can't connect any other network with this link, here or through the Connect API. | All |
redirect_url |
Where the page's final button goes. An http or https address. |
All |
show_calendar |
false hides the scheduled posts. |
All |
readonly_calendar |
true makes a view-only link: your client sees the connections and scheduled posts, but can't connect or disconnect anything. |
All |
language |
The page's language (see Languages). | All |
logo_image |
Your logo, as an http or https image URL. |
White-label |
connect_title |
The heading, up to 120 characters. | White-label |
connect_description |
The text under it, up to 500 characters. | White-label |
redirect_button_text |
The final button's label, up to 60 characters. | White-label |
connect_theme |
light, dark or auto (the default). |
White-label |
ui_labels |
Your own wording for any text on the page (see Your own wording). | White-label |
White-label is part of the Pro, Agency and Scale plans; those plans also drop the "Secure connection powered by Reelspan" line at the bottom of the page. On Free and Starter the link still works with Reelspan's look: the white-label fields are left out, and the answer says so in warnings.
{
"success": true,
"access_url": "https://app.reelspan.com/connect?token=…",
"duration": "48h",
"warnings": ["Branding (logo_image, connect_title) needs a plan with white-label; the link uses Reelspan's default look."]
}Settings are remembered
Branding is saved on the profile, so the next link for that profile only needs username. Fields you send change only themselves; the rest stay as they were. Send ui_labels: null (or {}) to clear your labels.
readonly_calendar is fixed into each link when it is made: changing it later doesn't change links already sent.
Languages
The hosted page comes in English (en), Spanish (es), German (de), French (fr), Portuguese (pt), Polish (pl) and Turkish (tr). Without language, it follows your client's browser language, and falls back to English.
Your own wording
ui_labels replaces any text on the page, in any language. Send up to 100 labels, each up to 300 characters:
{ "username": "my-brand", "ui_labels": { "connect.button": "Link account", "calendar.title": "Coming up" } }| Key | Default text |
|---|---|
connect.title |
Connect your social accounts |
connect.description |
Connect the accounts you want us to publish to. You can disconnect them here at any time. |
connect.button |
Connect |
connect.reconnect |
Reconnect |
connect.disconnect |
Disconnect |
connect.connected |
Connected |
connect.success |
connected. |
connect.cancelled |
was not connected: permission was not granted. |
connect.error |
could not be connected |
connect.expired |
This link has expired or is not valid. Ask for a new one. |
connect.done |
Done |
connect.maintenance |
Under maintenance. It can't be connected right now; check back soon. |
calendar.title |
Scheduled posts |
calendar.empty |
Nothing is scheduled. |
bluesky.handle |
Bluesky handle |
bluesky.password |
App password (Settings → Privacy and security → App passwords). Never your main password. |
footer.powered |
Secure connection powered by Reelspan |
connect_title and connect_description take priority over the connect.title and connect.description labels.
Embedding
The hosted page can be shown in an <iframe> on any https site, so your client can connect without leaving your app.
Your own connect page
To keep your client on your own site from start to finish, build the page yourself and use the Connect API for the OAuth networks:
- Start: call
POST /v1/oauth/{platform}/start. The answer has anauthorize_url. - Send your client to
authorize_url. They sign in on the network and allow access. - They come back to your
redirect_urlwith the outcome in the query string.
You can call it from your server with your API key and the profile:
curl -X POST https://api.reelspan.com/v1/oauth/youtube/start \
-H "Authorization: Bearer $REELSPAN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"profile": "my-brand", "redirect_url": "https://example.com/connected"}'const { authorize_url } = await reelspan.connect.oauthStart('youtube', { profile: 'my-brand', redirectUrl: 'https://example.com/connected' });authorize_url = reelspan.connect.oauth_start("youtube", profile="my-brand", redirect_url="https://example.com/connected")["authorize_url"]{ "success": true, "platform": "youtube", "authorize_url": "https://accounts.google.com/o/oauth2/v2/auth?…", "state": "q3J9…", "expires_in": 900 }Or, from your client's browser, with a connect link's token instead of your key. Take the token from a link's access_url (the token parameter) and send it as the Bearer token, with no profile:
curl -X POST https://api.reelspan.com/v1/oauth/youtube/start \
-H "Authorization: Bearer $CONNECT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"redirect_url": "https://example.com/connected"}'A token can do only what its link allows: the link's platforms, nothing on a read-only link, and at most 20 sign-ins every 10 minutes. If the profile has a redirect_url saved, a token's redirect_url must be on the same site, or the saved one is used.
The flow
authorize_urlworks once, for 15 minutes (expires_in: 900).platformis the network's id:youtube,tiktok,instagram,facebook,x(ortwitter),threads,linkedin,pinterest,redditorgoogle_business(orgoogle-business).- Always send
redirect_url. Without one, your client ends on the Reelspan dashboard.
When your client comes back, read these query parameters:
| Parameter | Value |
|---|---|
connect_status |
success, cancelled (they declined on the network) or error |
platform |
The network |
error_code |
On cancelled and error: for example ACCESS_DENIED, INVALID_STATE (the link expired or was used), PROVIDER_ERROR or CONNECTION_FAILED |
error_description |
On cancelled and error: a readable reason |
https://example.com/connected?connect_status=success&platform=youtubeThen confirm with GET /v1/profiles/{username}: the network's entry in social_accounts now has the account. Each connect and disconnect also fires the social_account_connected and social_account_disconnected webhooks.
Credentials networks on your page
Bluesky, Telegram, Discord and the other credentials networks have no sign-in to start: POST /v1/oauth/{platform}/start answers 400 NOT_OAUTH for them. Collect the fields on your page and send them from your server with Connect with credentials, or send your client the hosted link for those.
Check a token
GET /v1/connect-links/validate, with the connect token as the Bearer token, tells your page whether a link is still good and what the profile has connected:
curl https://api.reelspan.com/v1/connect-links/validate \
-H "Authorization: Bearer $CONNECT_TOKEN"const { profile } = await reelspan.connect.validate(connectToken);profile = reelspan.connect.validate(connect_token)["profile"]{
"success": true,
"isValid": true,
"profile": {
"username": "my-brand",
"social_accounts": {
"youtube": "",
"tiktok": { "username": "-000Abc…", "handle": "@mybrand", "display_name": "My Brand", "social_images": "https://…", "reauth_required": false, "capabilities": ["video", "draft", "video_privacy"] }
},
"ui_labels": {}
}
}An expired or altered token answers 401 with isValid: false.
Errors
| Status | error_code |
When |
|---|---|---|
| 400 | NOT_OAUTH |
The network connects with credentials, not a sign-in. |
| 400 | UNSUPPORTED_PLATFORM |
No such OAuth network. |
| 400 | PROVIDER_NOT_CONFIGURED |
The network can't be connected right now; the message says why. |
| 401 | The API key or connect token is invalid or expired. | |
| 403 | ACCESS_DENIED |
The token's link doesn't offer this network, or is read-only. |
| 403 | PROFILE_FORBIDDEN |
The API key is limited to other profiles. |
| 404 | PROFILE_NOT_FOUND |
No profile has that username. |
| 429 | RATE_LIMITED |
More than 20 sign-ins started with one link in 10 minutes. |
Creating links and starting sign-ins with an API key need the profiles scope.