# Social accounts Source: https://docs.native.no/native-api/social-accounts > **Preview.** The Native API is not live yet. This page describes the planned interface; names and fields can change before access opens. Send a client a link to connect a social account, handle what comes back to your app, and check that the account can publish. > **Not live for partners yet.** Partner access to the Native API is not live. Partner sign-in with client credentials and the versioned endpoint `/mcp/v1` are still in development. `create_connect_link` and `get_profile` are in Native’s MCP server today for people signed in to Native, not for partner backends. The `platform` and `returnUrl` arguments, `get_connect_options` and `complete_connect` are in development and not deployed. A brand publishes through the social accounts connected to it. Your client connects each account themselves: they log in with the platform and grant access there. You never handle their password. You start a connect with a link from `create_connect_link`, and you confirm it with `get_profile`. ## Two kinds of link | Detail | Link to Native’s connect page | Headless link | | --- | --- | --- | | Arguments | `brandId`, and optionally `returnUrl` | `brandId`, `platform` and `returnUrl` | | What the client sees | A Native page that lists every platform. They pick one and log in. | The platform’s own login and consent screen, straight away. No Native page. | | How you learn the result | Call `get_profile`. | The browser comes back to `returnUrl` with a `status`. Then confirm with `get_profile`. | | Status | In the gateway today for signed-in Native users, without `returnUrl`. | In development. | A headless link suits an app where your client never sees Native: they click "Connect Instagram" in your app, consent on Instagram, and land back in your app. ## How a headless connect works Headless links are in development. This is how they are planned to work: 1. Your backend calls `create_connect_link` with `brandId`, `platform` and `returnUrl`. 2. You send your client’s browser to the `url` it returns. Your backend never opens it. 3. The client logs in with the platform and grants access. 4. The browser comes back to `returnUrl` with `status` and `platform` added to it. 5. On `selection_required`, you list the client’s Pages or organisations with `get_connect_options`, they choose one, and you call `complete_connect`. 6. You call `get_profile` until the account’s channel is `connected` and `readiness.canPublish` is `true`. ## Create a connect link MCP tool `create_connect_link` (preview) Makes a single-use link your client opens to connect or reconnect a social account for one brand. It changes data, so the token needs the `mcp:write` scope. - Hand the `url` to your client. Do not open or fetch it yourself: loading it uses it up. - If nobody opens it, the link expires about 10 minutes after you make it. Once it is opened, the client has about 15 minutes to finish at the platform. - The link only connects an account to this brand. It gives nobody access to the brand or the workspace. - When a link expires, make a fresh one. A fresh link can always be made. Arguments: - `brandId` (string, required): The brand to connect an account to, as a UUID. - `platform` (string, optional; one of: `facebook`, `instagram`, `linkedin`, `twitter`, `tiktok`, `youtube`, `pinterest`, `reddit`, `threads`, `googlebusiness`): New, in development: makes the link headless. It skips Native’s page and opens this platform’s own login and consent screen. Requires `returnUrl`. `twitter` is X. These are lowercase channel names, as `get_profile` uses them. - `returnUrl` (string, optional): New, in development: where the browser goes when the connect is finished. An absolute https URL of 1 to 2,048 characters, with no user name or password in it and no spaces or control characters. Plain http works only for `localhost`, `127.0.0.1` and `[::1]`, and never in production. Bluesky is not on the `platform` list. It connects with an app password typed into Native’s own page, so use a link without `platform` for it. With `returnUrl` and no `platform`, the link opens Native’s connect page, and its Done button takes the client to `returnUrl`. Native adds nothing to the URL in that case. Call `get_profile` to see what was connected. A brand still on Native’s older social provider gets that provider’s own connect page instead, which has no Done button. Result data: - `url` (string, required): The link to hand to your client. A headless link has the form `https://app.native.no/e/connect//go` and sends the browser straight on to the platform. - `expiresAt` (ISO 8601 string, required): When the link expires if nobody opens it. About 10 minutes after you made it. - `singleUse` (boolean, required): Always `true`. - `brandId` (string, required): The brand. - `brandName` (string, required): The brand’s name. - `platform` (string | null, optional): New, in development: the platform a headless link opens, or `null` for a link to Native’s connect page. Absent when you sent neither `platform` nor `returnUrl`. Arguments: ```json { "brandId": "3f6c2a9e-8b1d-4c57-9e0a-2d4b7f1c8a63", "platform": "instagram", "returnUrl": "https://app.example.com/connect/done?client=4711" } ``` Result data: ```json { "url": "https://app.native.no/e/connect/q8Zt3VbN1xKc5mWp0rLs2A/go", "expiresAt": "2026-11-02T09:10:00.000Z", "singleUse": true, "brandId": "3f6c2a9e-8b1d-4c57-9e0a-2d4b7f1c8a63", "brandName": "Harbour Street Bakery", "platform": "instagram" } ``` ### Errors | Code | `retryable` | What it means | | --- | --- | --- | | `platform_not_supported` | `false` | `platform` is not one Native can open headless. `supportedChannels` lists the ones it can. The tool’s input schema refuses other values first, so you are unlikely to see this. | | `bad_request` | `false` | `platform` was sent without `returnUrl`, or `returnUrl` is not an address Native accepts. | | `headless_connect_unavailable` | `false` | Native could not make the link you asked for with `platform` or `returnUrl`, for example because the brand is still on Native’s older social provider. You got no link. Call again without `platform` and `returnUrl` for a link to Native’s connect page. | ## What comes back to your return URL When a headless connect ends, Native sends the browser to `returnUrl` with query parameters added. `status` says how it ended, and `platform` is always there. | `status` | Other parameters | What to do | | --- | --- | --- | | `connected` | `platform` | Confirm with `get_profile`. | | `selection_required` | `platform`, `connectToken` | The client manages several Facebook Pages, LinkedIn organisations, Pinterest boards or Google Business locations, and has to pick one. Pass `connectToken` to `get_connect_options`, let the client choose, then call `complete_connect`. | | `failed` | `platform`, `error` | Nothing was connected. `error` says why. Make a fresh link to try again. | Examples: ```text https://app.example.com/connect/done?client=4711&status=connected&platform=instagram https://app.example.com/connect/done?client=4711&status=selection_required&platform=linkedin&connectToken=... https://app.example.com/connect/done?client=4711&status=failed&platform=tiktok&error=oauth_denied ``` Native first removes any `status`, `platform`, `connectToken` or `error` already on your URL, then adds its own. Your other query parameters stay. So you can put your own reference to the client in `returnUrl`, as `client=4711` does above. > **Check before you trust it.** Anyone can open your `returnUrl` with parameters they made up. Treat `status=connected` as a reason to check, and confirm with `get_profile` before you tell your client the account is connected. If your client opens a headless link after it has expired or been used, Native still sends the browser back to `returnUrl`, with `status=failed` and `error=link_expired`. It does this for up to an hour after you made the link. After that, the link opens Native’s own expired-link page. ### Failure codes | `error` | What happened | | --- | --- | | `oauth_denied` | The client declined or cancelled on the platform’s screen. | | `link_expired` | The link had expired or been used before the client got through. | | `account_in_another_workspace` | The account is already connected to a profile in another Native workspace, so it was not added here. Instagram, TikTok and X connect whichever account the browser is logged in to, so the client may need to log out there and log in to the right account first. | | `no_facebook_pages` | Facebook gave access to no Pages. The client should connect again and select their Pages on Facebook’s screen. | | `no_google_locations` | The Google account manages no Google Business location. | | `missing_google_permissions` | Google did not grant every permission Native asked for. The client should connect again and tick every box on Google’s screen. | | `connection_failed` | Anything else, at the platform or at Native. | Treat `error` as an open set. Some codes come from Native’s social provider, and new ones can appear. Whatever the code, a fresh link is the way to try again. ## List what the client can connect MCP tool `get_connect_options` (in development) After `status=selection_required`, lists the Pages, organisations, boards or locations the client can connect to the brand. Show them to the client and let them choose. Reading the options changes nothing, and you can call it again. Arguments: - `brandId` (string, required): The brand, as a UUID. - `connectToken` (string, required): The `connectToken` query parameter from `returnUrl`, exactly as it came. 1 to 8,192 characters. It lasts about 10 minutes, and only the caller that made the link can use it. Result data: - `brandId` (string, required): The brand. - `platform` (string, required): The platform, such as `linkedin`. - `options` (object[], required): What the client can choose from. - `id` (string, required): Pass the chosen one to `complete_connect` as `selectionId`. - `name` (string, required): The name to show. - `kind` (string, required; one of: `page`, `organization`, `personal`, `board`, `location`): `page`: a Facebook Page. `organization`: a LinkedIn organisation. `personal`: the client’s own LinkedIn profile. `board`: a Pinterest board. `location`: a Google Business location. - `pictureUrl` (string | null, required): A picture to show beside the name, when the platform gives one. - `detail` (string | null, required): A Page’s category or a location’s address. `null` for the others. For LinkedIn, the client’s own profile comes first, with `kind: "personal"` and `id: "__personal__"`. Result data: ```json { "brandId": "3f6c2a9e-8b1d-4c57-9e0a-2d4b7f1c8a63", "platform": "linkedin", "options": [ { "id": "__personal__", "name": "Kari Nordmann", "kind": "personal", "pictureUrl": null, "detail": null }, { "id": "urn:li:organization:81234567", "name": "Harbour Street Bakery", "kind": "organization", "pictureUrl": null, "detail": null } ] } ``` ### Errors | Code | `retryable` | What it means | | --- | --- | --- | | `connect_session_expired` | `false` | The `connectToken` has expired, or its link has died. Nothing was connected. Make a fresh link with `create_connect_link` and have the client go through the platform again. | ## Finish the connect MCP tool `complete_connect` (in development) Connects the option your client chose to the brand. It changes data, so the token needs the `mcp:write` scope. It replaces any account the brand already had on that platform. Only call it with your client’s own choice. Arguments: - `brandId` (string, required): The brand, as a UUID. - `connectToken` (string, required): The same `connectToken` you passed to `get_connect_options`. 1 to 8,192 characters. - `selectionId` (string, required): The `id` of the option your client chose, from `get_connect_options`. 1 to 200 characters. `__personal__` connects a LinkedIn member’s own profile. Result data: - `brandId` (string, required): The brand. - `platform` (string, required): The platform. - `connected` (boolean, required): Always `true`. - `account` (object, required): The account that is now connected. - `displayName` (string | null, required): The account’s name, when the platform gives one. - `username` (string | null, required): The account’s handle, when it has one. Arguments: ```json { "brandId": "3f6c2a9e-8b1d-4c57-9e0a-2d4b7f1c8a63", "connectToken": "c2VhbGVkLWNvbm5lY3QtdG9rZW4tZXhhbXBsZQ", "selectionId": "urn:li:organization:81234567" } ``` Result data: ```json { "brandId": "3f6c2a9e-8b1d-4c57-9e0a-2d4b7f1c8a63", "platform": "linkedin", "connected": true, "account": { "displayName": "Harbour Street Bakery", "username": null } } ``` ### Errors | Code | `retryable` | What it means | | --- | --- | --- | | `connect_session_expired` | `false` | The `connectToken` has expired, or its link has died. Nothing was connected. Make a fresh link with `create_connect_link`. | | `bad_request` | `false` | `selectionId` is not one of the options `get_connect_options` returned. `invalidFields` names `selectionId`. | ## Confirm the connection MCP tool `get_profile` (preview) `get_profile` shows what a brand can do right now, including each connected account and its health. After `status=connected`, or after `complete_connect`, call it once. If the account is not ready yet, call it every 10 seconds for up to 2 minutes. The account is ready when its channel has `health: "connected"` and `readiness.canPublish` is `true`. A `failed` status needs a fresh link, not more polling. Arguments: - `brandId` (string, required): The brand. - `includeScopes` (boolean, optional; default: `false`): Also fetch the permissions each account granted. It costs one extra call to Native’s social provider per connected account, so leave it off for routine checks. The result data has more than this, such as `brandName` and `generationPlatforms`. [Profiles](https://docs.native.no/native-api/profiles#get-a-profile) has the full reference. These are the fields that tell you whether a connect worked: Result data, the connection fields: - `readiness` (object, required): Whether the brand can make and publish posts. - `canGenerate` (boolean, required): Whether Native can make posts for the brand. - `canPublish` (boolean, required): Whether the brand has an account it can publish to. - `blockers` (string[], required): What stands in the way, as codes. Treat the list as open. `no_channel_connected` and `all_channels_need_reauth` both mean: send your client a connect link. `connection_check_failed` means Native could not check the accounts: call again later, and do not send a connect link. New, in development: `profile_not_ready` while Native is still building a profile from a website. [Readiness blockers](https://docs.native.no/native-api/profiles#readiness-blockers) lists every code. - `channels` (object[], required): One entry per connected account, including accounts that need reconnecting and accounts that were disconnected. - `platform` (string, required): The lowercase channel name, such as `instagram`. - `displayName` (string | null, required): The account’s name. - `username` (string | null, required): The account’s handle. - `profileUrl` (string | null, required): A link to the account on the platform. - `health` (string, required; one of: `connected`, `expiring_soon`, `needs_reauth`, `disconnected`): `connected` and `expiring_soon` publish. `needs_reauth` and `disconnected` do not. - `healthReason` (string | null, required): A short reason for the health, when there is one. - `connectedAt` (ISO 8601 string | null, required): When the account was connected. - `tokenRefreshInDays` (number | null, required): Days until the platform’s access needs renewing. `null` when the platform does not report it. - `tokenExpiresAt` (ISO 8601 string | null, optional): When the platform’s access runs out. May be absent. - `scopes` (string[] | null, required): The permissions the account granted. Filled only with `includeScopes`. `null` means unknown, not none. - `missingScopes` (string[] | null, optional): Permissions Native needs that the account did not grant. May be absent. - `canPost` (boolean | null, optional): Whether Native can post to the account. May be absent. - `canFetchAnalytics` (boolean | null, optional): Whether Native can read the account’s analytics. May be absent. - `storyDestinations` (string[] | null, optional): The story destinations the brand can use, such as `instagram_stories` and `facebook_stories`. May be absent. Part of the result data: ```json { "readiness": { "canGenerate": true, "canPublish": true, "blockers": [] }, "channels": [ { "platform": "instagram", "displayName": "Harbour Street Bakery", "username": "harbourstreetbakery", "profileUrl": "https://www.instagram.com/harbourstreetbakery", "health": "connected", "healthReason": null, "connectedAt": "2026-11-02T09:04:12.000Z", "tokenRefreshInDays": 58, "scopes": null } ], "storyDestinations": [ "instagram_stories" ] } ``` Stories have no connect link of their own. Instagram and Facebook stories publish through the Instagram and Facebook accounts, and never appear as channels. Channel names in `get_profile` are lowercase, such as `instagram`. A post’s destinations use uppercase names, such as `INSTAGRAM`. ## A full headless connect This sketch uses `client`, an official MCP SDK client connected with a partner token, as in [Calling a tool](https://docs.native.no/native-api#calling-a-tool). Make the link and send your client’s browser to it: Make the link: ```ts const brandId = "3f6c2a9e-8b1d-4c57-9e0a-2d4b7f1c8a63"; const minted = await client.callTool({ name: "create_connect_link", arguments: { brandId, platform: "linkedin", returnUrl: "https://app.example.com/connect/done?client=4711", }, }); const link = (minted.structuredContent as { data: any }).data; if (minted.isError) throw new Error(link.error.code); // Redirect your client's browser to link.url. Never fetch it from your backend. ``` When the browser comes back, read `status` from the URL: Handle the return: ```ts async function onConnectReturn(returned: URL, brandId: string) { const status = returned.searchParams.get("status"); if (status === "failed") { // Offer a fresh link. The error parameter says why it failed. return { state: "failed", error: returned.searchParams.get("error") }; } if (status === "selection_required") { const connectToken = returned.searchParams.get("connectToken") ?? ""; const listed = await client.callTool({ name: "get_connect_options", arguments: { brandId, connectToken }, }); const data = (listed.structuredContent as { data: any }).data; if (listed.isError) return { state: "failed", error: data.error.code }; // Show data.options to your client. Keep connectToken until they choose. return { state: "choose", options: data.options, connectToken }; } // "connected", or anything else: check the profile before you trust it. return { state: "check" }; } ``` Once your client has chosen, finish the connect. Then wait until the account can publish: Finish and confirm: ```ts async function finishConnect(brandId: string, connectToken: string, selectionId: string) { const done = await client.callTool( { name: "complete_connect", arguments: { brandId, connectToken, selectionId }, }, undefined, { timeout: 150_000 }, ); const data = (done.structuredContent as { data: any }).data; // connect_session_expired: make a fresh link and start again. if (done.isError) throw new Error(data.error.code); } async function waitUntilPublishable(brandId: string, platform: string) { for (let attempt = 0; attempt < 12; attempt++) { const result = await client.callTool({ name: "get_profile", arguments: { brandId } }); const profile = (result.structuredContent as { data: any }).data; if (result.isError) throw new Error(profile.error.code); const channel = profile.channels.find((c: { platform: string }) => c.platform === platform); if (channel?.health === "connected" && profile.readiness.canPublish) return true; await new Promise((resolve) => setTimeout(resolve, 10_000)); } return false; // Not ready after 2 minutes: offer a fresh link. } ``` ## Reconnecting an account Platforms take access back after a while, and people change passwords. Check channel health with `get_profile` once a day, and before you schedule. | `health` | Publishes | What to do | | --- | --- | --- | | `connected` | Yes | Nothing. | | `expiring_soon` | Yes | Send your client a link for that platform before `tokenExpiresAt`, so publishing does not stop. | | `needs_reauth` | No | Send your client a link for that platform. They log in with the same account. | | `disconnected` | No | Send your client a link for that platform. | A reconnect uses the same `create_connect_link` call as a first connect. Tell your client which account to reconnect: `get_profile` names it. ## Other errors Every tool on this page can also return the errors any tool can: `bad_request`, `not_found`, `forbidden`, `subscription_required`, `internal_error`, `upstream_timeout`, `upstream_unavailable`, `upstream_identity_failed`, `identity_provider_unavailable` and `upstream_`. `create_connect_link` and `complete_connect` can return `writes_disabled`. [Errors](https://docs.native.no/native-api/errors) explains each one.