# Social Accounts

Connect social accounts, find their IDs, and handle accounts that need reconnecting.


A social account is one connected profile, page or channel. Every post targets accounts by their Relaycast `id`.

## Supported platforms

| Platform | `platform` value | How to connect |
| --- | --- | --- |
| X (Twitter) | `twitter` | Dashboard (OAuth) |
| Instagram | `instagram` | Dashboard (OAuth) |
| Facebook | `facebook` | Dashboard (OAuth) |
| LinkedIn | `linkedin` | Dashboard (OAuth) |
| TikTok | `tiktok` | Dashboard (OAuth) |
| YouTube | `youtube` | Dashboard (OAuth) |
| Threads | `threads` | Dashboard (OAuth) |
| Pinterest | `pinterest` | Dashboard (OAuth) |
| Google Business Profile | `google_business` | Dashboard (OAuth) |
| Bluesky | `bluesky` | Dashboard or API (app password) |

Each platform has its own requirements and limits. See the [platform guides](/docs/platforms).

## Connect in the dashboard

1. Open **Integrations** at [https://social-publish-kappa.vercel.app/dashboard/integrations](https://social-publish-kappa.vercel.app/dashboard/integrations).
2. Click **New integration** and choose a platform.
3. Optionally pick a [social group](/docs/groups) and, if you've added one, your own [OAuth app](/docs/oauth-apps).
4. Approve access on the platform's consent screen. You return to the dashboard with the account connected.

Only project owners and admins can connect or disconnect accounts.

## Let your users connect with a link

OAuth needs the account owner to approve access in a browser, so the API can't connect an OAuth account on its own. Instead, create a **connect link** with [`POST /v1/accounts/connect-links`](/docs/api/create-connect-link) and send your user to it. They approve access on the platform's consent screen, without a Relaycast account, and come back to your `redirectUrl` with the new account's `id`:

```bash
curl -X POST https://social-publish-api.onrender.com/v1/accounts/connect-links   -H "x-access-key: $RELAYCAST_API_KEY"   -H "content-type: application/json"   -d '{ "platform": "youtube", "redirectUrl": "https://app.example.com/integrations/done" }'
```

Each link works once and expires (60 minutes by default).

## Connect Bluesky with the API

Bluesky uses app passwords instead of OAuth, so you can connect it with a request. Create an app password in Bluesky's settings, then send your handle and the app password:

```bash tab="cURL"
curl -X POST https://social-publish-api.onrender.com/v1/accounts/bluesky \
  -H "x-access-key: $RELAYCAST_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "identifier": "yourname.bsky.social", "appPassword": "xxxx-xxxx-xxxx-xxxx" }'
```

```ts tab="Node.js"
const res = await fetch("https://social-publish-api.onrender.com/v1/accounts/bluesky", {
  method: "POST",
  headers: { "x-access-key": process.env.RELAYCAST_API_KEY!, "Content-Type": "application/json" },
  body: JSON.stringify({ identifier: "yourname.bsky.social", appPassword: process.env.BLUESKY_APP_PASSWORD }),
});
const { data: account } = await res.json();
```

```python tab="Python"
res = requests.post(
    "https://social-publish-api.onrender.com/v1/accounts/bluesky",
    headers={"x-access-key": os.environ["RELAYCAST_API_KEY"]},
    json={"identifier": "yourname.bsky.social", "appPassword": os.environ["BLUESKY_APP_PASSWORD"]},
)
account = res.json()["data"]
```

A successful call returns `201` with the account. If Bluesky rejects the credentials you get `400 invalid_credentials`. Connecting an account that's already connected updates its credentials and keeps the same `id`. See [Connect Bluesky](/docs/api/connect-bluesky).

## List accounts

`GET /v1/accounts` returns every account in the project that isn't disconnected:

```bash
curl https://social-publish-api.onrender.com/v1/accounts -H "x-access-key: $RELAYCAST_API_KEY"
```

```json
{
  "data": [
    {
      "id": "8f1c2a4e-5b6d-4e7f-9a0b-1c2d3e4f5a6b",
      "platform": "instagram",
      "handle": "acme.studio",
      "displayName": "Acme Studio",
      "avatarUrl": "https://...",
      "status": "active",
      "groupId": "c3d4e5f6-0000-4000-8000-000000000001",
      "oauthAppId": null,
      "createdAt": "2026-09-30T10:12:00.000Z"
    }
  ]
}
```

| Field | Description |
| --- | --- |
| `id` | Use this as `targets[].accountId`. |
| `platform` | One of the values in the table above. |
| `handle`, `displayName`, `avatarUrl` | Profile details from the platform at connect time. |
| `status` | `active` or `reconnect_required`. |
| `groupId` | The [social group](/docs/groups), or `null` for Default. |
| `oauthAppId` | Your own [OAuth app](/docs/oauth-apps) used to connect it, or `null` for the Relaycast app. |

There's no pagination: all accounts are returned in one response. See [List accounts](/docs/api/list-accounts).

## Reconnecting accounts

If a platform revokes access (the user changed their password, removed the app, or a token can't be refreshed), the failing target gets `errorCode: "auth_revoked"` and the account's status changes to `reconnect_required`. The dashboard marks it **Reconnect needed**.

Posts that target an inactive account are rejected up front:

```json
{
  "error": {
    "code": "account_inactive",
    "message": "Some accounts need reconnecting",
    "details": { "accountIds": ["8f1c2a4e-5b6d-4e7f-9a0b-1c2d3e4f5a6b"] }
  }
}
```

Reconnect by connecting the same profile again from Integrations. Reconnecting the same platform account keeps its `id`, so you don't need to update stored IDs.

## Disconnecting

Disconnecting an account in the dashboard removes its stored credentials and hides it from `GET /v1/accounts`. Past posts that used it stay in your history. There's no API endpoint to disconnect an account.
