Native APITools
PreviewSocial accounts
Send a client a link to connect a social account, handle what comes back to your app, and check that the account can publish.
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:
- Your backend calls
create_connect_linkwithbrandId,platformandreturnUrl. - You send your client’s browser to the
urlit returns. Your backend never opens it. - The client logs in with the platform and grants access.
- The browser comes back to
returnUrlwithstatusandplatformadded to it. - On
selection_required, you list the client’s Pages or organisations withget_connect_options, they choose one, and you callcomplete_connect. - You call
get_profileuntil the account’s channel isconnectedandreadiness.canPublishistrue.
Create a connect link
#create_connect_linkPreviewMakes 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
urlto 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
brandIdstringRequiredThe brand to connect an account to, as a UUID.
platformstringOptionalNew, in development: makes the link headless. It skips Native’s page and opens this platform’s own login and consent screen. Requires
returnUrl.twitteris X. These are lowercase channel names, asget_profileuses them.One of:
facebook,instagram,linkedin,twitter,tiktok,youtube,pinterest,reddit,threads,googlebusinessreturnUrlstringOptionalNew, 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.1and[::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
urlstringRequiredThe link to hand to your client. A headless link has the form
https://app.native.no/e/connect/<token>/goand sends the browser straight on to the platform.expiresAtISO 8601 stringRequiredWhen the link expires if nobody opens it. About 10 minutes after you made it.
singleUsebooleanRequiredAlways
true.brandIdstringRequiredThe brand.
brandNamestringRequiredThe brand’s name.
platformstring | nullOptionalNew, in development: the platform a headless link opens, or
nullfor a link to Native’s connect page. Absent when you sent neitherplatformnorreturnUrl.
Arguments
{
"brandId": "3f6c2a9e-8b1d-4c57-9e0a-2d4b7f1c8a63",
"platform": "instagram",
"returnUrl": "https://app.example.com/connect/done?client=4711"
}Result data
{
"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
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_deniedNative 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.
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
#get_connect_optionsIn developmentAfter 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
brandIdstringRequiredThe brand, as a UUID.
connectTokenstringRequiredThe
connectTokenquery parameter fromreturnUrl, 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
brandIdstringRequiredThe brand.
platformstringRequiredThe platform, such as
linkedin.optionsobject[]RequiredWhat the client can choose from.
idstringRequiredPass the chosen one to
complete_connectasselectionId.namestringRequiredThe name to show.
kindstringRequiredpage: a Facebook Page.organization: a LinkedIn organisation.personal: the client’s own LinkedIn profile.board: a Pinterest board.location: a Google Business location.One of:
page,organization,personal,board,locationpictureUrlstring | nullRequiredA picture to show beside the name, when the platform gives one.
detailstring | nullRequiredA Page’s category or a location’s address.
nullfor the others.
For LinkedIn, the client’s own profile comes first, with kind: "personal" and id: "__personal__".
Result data
{
"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
#complete_connectIn developmentConnects 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
brandIdstringRequiredThe brand, as a UUID.
connectTokenstringRequiredThe same
connectTokenyou passed toget_connect_options. 1 to 8,192 characters.selectionIdstringRequiredThe
idof the option your client chose, fromget_connect_options. 1 to 200 characters.__personal__connects a LinkedIn member’s own profile.
Result data
brandIdstringRequiredThe brand.
platformstringRequiredThe platform.
connectedbooleanRequiredAlways
true.accountobjectRequiredThe account that is now connected.
displayNamestring | nullRequiredThe account’s name, when the platform gives one.
usernamestring | nullRequiredThe account’s handle, when it has one.
Arguments
{
"brandId": "3f6c2a9e-8b1d-4c57-9e0a-2d4b7f1c8a63",
"connectToken": "c2VhbGVkLWNvbm5lY3QtdG9rZW4tZXhhbXBsZQ",
"selectionId": "urn:li:organization:81234567"
}Result data
{
"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
#get_profilePreviewget_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
brandIdstringRequiredThe brand.
includeScopesbooleanOptionalAlso 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.
Default:
false
The result data has more than this, such as brandName and generationPlatforms. Profiles has the full reference. These are the fields that tell you whether a connect worked:
Result data, the connection fields
readinessobjectRequiredWhether the brand can make and publish posts.
canGeneratebooleanRequiredWhether Native can make posts for the brand.
canPublishbooleanRequiredWhether the brand has an account it can publish to.
blockersstring[]RequiredWhat stands in the way, as codes. Treat the list as open.
no_channel_connectedandall_channels_need_reauthboth mean: send your client a connect link.connection_check_failedmeans Native could not check the accounts: call again later, and do not send a connect link. New, in development:profile_not_readywhile Native is still building a profile from a website. Readiness blockers lists every code.
channelsobject[]RequiredOne entry per connected account, including accounts that need reconnecting and accounts that were disconnected.
platformstringRequiredThe lowercase channel name, such as
instagram.displayNamestring | nullRequiredThe account’s name.
usernamestring | nullRequiredThe account’s handle.
profileUrlstring | nullRequiredA link to the account on the platform.
healthstringRequiredconnectedandexpiring_soonpublish.needs_reauthanddisconnecteddo not.One of:
connected,expiring_soon,needs_reauth,disconnectedhealthReasonstring | nullRequiredA short reason for the health, when there is one.
connectedAtISO 8601 string | nullRequiredWhen the account was connected.
tokenRefreshInDaysnumber | nullRequiredDays until the platform’s access needs renewing.
nullwhen the platform does not report it.tokenExpiresAtISO 8601 string | nullOptionalWhen the platform’s access runs out. May be absent.
scopesstring[] | nullRequiredThe permissions the account granted. Filled only with
includeScopes.nullmeans unknown, not none.missingScopesstring[] | nullOptionalPermissions Native needs that the account did not grant. May be absent.
canPostboolean | nullOptionalWhether Native can post to the account. May be absent.
canFetchAnalyticsboolean | nullOptionalWhether Native can read the account’s analytics. May be absent.
storyDestinationsstring[] | nullOptionalThe story destinations the brand can use, such as
instagram_storiesandfacebook_stories. May be absent.
Part of the result data
{
"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. Make the link and send your client’s browser to it:
Make the link
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
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
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_<status>. create_connect_link and complete_connect can return writes_disabled. Errors explains each one.
The Native API is not open yet
Partner access is being built now. Tell us what you would build, and we will be in touch when it opens.