Native APITools
PreviewProfiles
Create a profile for each client from its website, wait for Native to build it, check what it can do, and end the service when the client leaves.
A profile is one client’s presence on Native: its brand settings, its connected social accounts and its posts. The tools call it a brand, so a profile’s id is the brandId that every other tool takes.
Each client gets a workspace of its own with one profile in it. The workspace’s id is companyId. You make the profile with create_profile, watch it with get_profile, and end the service with deactivate_profile or delete_profile.
The life of a profile
#| Step | Call | What happens |
|---|---|---|
| Create | create_profile | Native answers at once with a brandId and starts building the profile from the website. |
| Wait | get_profile | Poll about once a minute until onboarding.status is ready or failed. It usually takes 10 to 30 minutes. |
| Use | get_profile | Check readiness before you generate or schedule. Check channel health once a day. |
| End | deactivate_profile or delete_profile | Native withdraws scheduled posts and disconnects the social accounts in the background. Delete also erases everything Native holds for the client. |
You can create connect links for the client’s social accounts while the profile builds. The whole sequence, from onboarding to publishing, is in the flow.
Create a profile
#create_profileIn developmentCreates a profile for a client from its website. Native makes a workspace of its own for the client, on your plan, with no browser and no checkout. It answers at once and builds the profile in the background. It reads the website and fills in the business profile, the brand look, knowledge, products and writing style, the same as for a customer who signs up in the Native app.
Only a partner token can call it. A token without partner access does not see it in tools/list, and calling it anyway fails as an unknown tool. It changes data, so the token needs the mcp:write scope (see Authentication).
Input
websiteUrlstringRequiredThe client’s own website, such as
https://harbourstreetbakery.example. A bare domain is accepted. Not a social media profile. 1 to 2,048 characters.namestringRequiredThe client’s business name. Native uses it for the workspace and its profile. 1 to 100 characters.
externalIdstringRequiredYour own id for this client, 1 to 200 characters. It makes the call idempotent, and it is the only way to retry a failed build. See Calling it again.
allowTeamPhotosbooleanOptionalLets Native read and keep pictures of the client’s staff from the website, and recognise them in pictures you upload later. Leave it off unless the client has agreed.
Default:
false
Output
brandIdstringRequiredThe profile’s id. Every other tool takes it.
companyIdstringRequiredThe id of the client’s workspace.
statusstringRequiredbuildingon a new profile, and after a repeat that restarts a failed build. On any other repeat, the build’s current state.failedhere means Native could not start the build.One of:
building,ready,failederrorstringOptionalPresent only when
statusisfailed. One of the build failure codes, in practicebuild_not_queued. Treat it as an open set.createdbooleanRequiredfalsewhen the call answered with the profile an earlier call with the sameexternalIdmade.
Create a profile
import type { Client } from "@modelcontextprotocol/sdk/client/index.js";
// client: an MCP SDK client connected with a partner token.
async function createProfile(client: Client) {
const result = await client.callTool(
{
name: "create_profile",
arguments: {
websiteUrl: "https://harbourstreetbakery.example",
name: "Harbour Street Bakery",
externalId: "client-4821",
},
},
undefined,
{ timeout: 150_000 },
);
const data = (result.structuredContent as { data: any } | undefined)?.data;
if (result.isError) {
// data = { ok: false, error: { code, message, retryable, ... } }
throw new Error("create_profile failed: " + data.error.code);
}
// Keep these next to your own id for the client.
return { brandId: data.brandId, companyId: data.companyId, status: data.status };
}structuredContent
{
"data": {
"brandId": "3f6c2a9e-5b7d-4e1a-9c2f-7a1d8e4b6c05",
"companyId": "b8e1d0c4-2f3a-4c5d-9e6f-0a1b2c3d4e5f",
"status": "building",
"created": true
}
}Calling it again
#externalId makes the call idempotent. A repeat with the same externalId answers with the profile the first call made, with created: false, whatever else it sends. It never makes a second workspace for the same externalId.
A repeat is also the only way to retry a failed build. Call again with the same externalId and the build restarts where it stopped. The answer then says building again.
A repeat always builds from the website the first call sent, so it cannot fix a wrong address. No partner tool can change a profile’s website yet. Contact Native if the first address was wrong.
Errors
#| Code | Retryable | Meaning |
|---|---|---|
partner_only | false | Native refused the call because the caller is not a partner. Nothing was created. |
invalid_website_url | false | Native refused the website before creating anything. websiteRefusal says why. Send the client’s own website. |
bad_request | false | Native could not accept the input, such as a websiteUrl that is not a valid website address. invalidFields names the fields to fix, and fieldErrors says why when it is present. |
invalid_website_url carries one of these codes in websiteRefusal. The field is left out when Native sends a reason the gateway does not know.
| Code | Meaning |
|---|---|
BLOCKED_DOMAIN | A social media or platform address, not the client’s own site. |
URL_NOT_REACHABLE | The address does not resolve, or it refused the connection. |
URL_UNSAFE | The address is flagged as harmful. |
URL_NOT_ALLOWED | The address points at a private network address. |
structuredContent on a refusal
{
"data": {
"ok": false,
"error": {
"code": "invalid_website_url",
"message": "That website address was refused, so no profile was created; websiteRefusal says why: BLOCKED_DOMAIN is a social media or platform address rather than the customer's own site, URL_NOT_REACHABLE does not resolve or refused the connection, URL_UNSAFE is flagged as harmful, URL_NOT_ALLOWED points at a private address. Send the customer's own website",
"retryable": false,
"websiteRefusal": "BLOCKED_DOMAIN"
}
}
}It can also return the errors every tool can return.
Wait for the build
#While the build runs, get_profile reports onboarding.status as building, readiness.canGenerate as false, and the blocker profile_not_ready.
Until the build is ready, every tool that generates refuses the profile with profile_not_ready: create_posts, generate_caption, generate_image, regenerate_post_image, ad generation, and create_draft when it is sent a prompt or includeImage: true. That includes a profile whose build has failed. A create_draft with your own caption is not refused. Connecting the client’s accounts and publishing are not held up either.
Poll get_profile about once a minute until onboarding.status is ready or failed. It usually takes 10 to 30 minutes. profile_not_ready is retryable, so a call refused for it can be made again unchanged once the profile is ready.
get_profile while the build runs
{
"data": {
"brandId": "3f6c2a9e-5b7d-4e1a-9c2f-7a1d8e4b6c05",
"brandName": "Harbour Street Bakery",
"companyId": "b8e1d0c4-2f3a-4c5d-9e6f-0a1b2c3d4e5f",
"isActive": true,
"defaultPlatform": null,
"generationPlatforms": [],
"storyDestinations": [],
"socialProvider": "ZERNIO",
"channels": [],
"readiness": {
"canGenerate": false,
"canPublish": false,
"blockers": [
"profile_not_ready",
"no_channel_connected"
]
},
"onboarding": {
"status": "building"
}
}
}Wait for the build
import type { Client } from "@modelcontextprotocol/sdk/client/index.js";
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
async function waitForProfile(client: Client, brandId: string) {
for (;;) {
const result = await client.callTool(
{ name: "get_profile", arguments: { brandId } },
undefined,
{ timeout: 150_000 },
);
const data = (result.structuredContent as { data: any } | undefined)?.data;
if (result.isError) {
if (!data.error.retryable) throw new Error("get_profile failed: " + data.error.code);
} else {
const onboarding = data.onboarding;
// No onboarding field: create_profile did not build this profile.
if (!onboarding || onboarding.status === "ready") return data;
if (onboarding.status === "failed") {
// Retry: call create_profile again with the same externalId.
throw new Error("build failed: " + onboarding.error);
}
}
await sleep(60_000);
}
}When a build fails
#A failed build reports a code in onboarding.error. Treat the list as open: Native can add codes. A repeat create_profile restarts the build and answers building, so read the code from get_profile before you retry.
| Code | Meaning |
|---|---|
website_unreachable | Nothing could read the website. A retry helps only once the site answers again. |
analysis_failed | Native read the website, but the analysis of it failed. |
enrichment_failed | Native could not fill in the profile from the analysis. |
build_not_queued | Native could not start the next step of the build. |
build_interrupted | The build stopped without finishing, for example because the server running it restarted. |
Every one of them is retried the same way: call create_profile again with the same externalId. The build picks up where it stopped.
Get a profile
#get_profilePreviewShows what a profile can do right now: its connected channels and their health, the channels it can publish to, and whether it can generate and publish. Call it while a build runs, before you schedule, and once a day to catch accounts that need reconnecting.
Input
brandIdstringRequiredThe profile’s id.
includeScopesbooleanOptionalAlso fetch the OAuth scopes each channel granted. It costs one call to the social provider per channel, so leave it off for routine checks.
Default:
false
Output
brandIdstringRequiredThe profile’s id.
brandNamestringRequiredThe profile’s name.
companyIdstringRequiredThe workspace the profile belongs to.
isActivebooleanRequiredWhether the profile is active. An inactive profile cannot generate or publish.
defaultPlatformstring | nullRequiredWhere a post goes when a call names no destinations, as an uppercase destination name such as
INSTAGRAM.nullwhen none is set.generationPlatformsstring[]RequiredThe channels the profile can publish to right now, in lowercase, such as
instagram. A channel that needs reconnecting is left out.storyDestinationsstring[] | nullOptionalThe story destinations the profile can publish to, in lowercase. Stories use the base Instagram or Facebook connection and never appear as channels of their own. May be absent.
One of:
instagram_stories,facebook_storiessocialProviderstringRequiredThe provider Native publishes through for this profile, such as
ZERNIO.channelsobject[]RequiredThe connected social accounts, one per channel.
platformstringRequiredThe channel, in lowercase, such as
instagram.displayNamestring | nullRequiredThe account’s display name.
usernamestring | nullRequiredThe account’s username.
profileUrlstring | nullRequiredA link to the account.
healthstringRequiredWhether the account can publish. See Channel health.
One of:
connected,expiring_soon,needs_reauth,disconnectedhealthReasonstring | nullRequiredA code saying why
healthis notconnected, such astoken_expired_or_revokedortoken_refresh_due.nullwhen it isconnected. Treat it as an open set.connectedAtISO 8601 string | nullRequiredWhen the account was connected.
tokenRefreshInDaysnumber | nullRequiredDays until the account’s token has to be refreshed.
nullmeans the provider does not report it, not that it never expires.tokenExpiresAtISO 8601 string | nullOptionalWhen the account’s token expires, as the provider reports it.
nullmeans not reported. May be absent.scopesstring[] | nullRequiredThe OAuth scopes the account granted. Filled only with
includeScopes.nullmeans unknown, not none.missingScopesstring[] | nullOptionalScopes Native needs that the account has not granted.
nullwhen the scopes are unknown. May be absent.canPostboolean | nullOptionalThe provider’s own verdict on whether the account can post.
nullwhen the provider gave none. May be absent.canFetchAnalyticsboolean | nullOptionalThe provider’s own verdict on whether Native can read the account’s analytics.
nullwhen the provider gave none. May be absent.
readinessobjectRequiredWhether the profile can generate and publish now, and what stands in the way.
canGeneratebooleanRequiredWhether Native can generate posts for the profile now.
canPublishbooleanRequiredWhether the profile can publish now: it is active, has brand settings and has at least one channel it can publish to.
blockersstring[]RequiredWhat stands in the way, as codes. Empty when nothing does. See Readiness blockers.
onboardingobjectOptionalNew, in development. How the build
create_profilestarted is going. Only on a profilecreate_profilemade, and absent for every other profile.statusstringRequiredbuildingwhile Native reads the website and fills in the profile.One of:
building,ready,failederrorstringOptionalPresent only when
statusisfailed. One of the build failure codes.
The output can hold more fields than these. Ignore any you do not use.
A profile create_profile built, once it is ready and has one Instagram account connected. The onboarding field is in development.
structuredContent
{
"data": {
"brandId": "3f6c2a9e-5b7d-4e1a-9c2f-7a1d8e4b6c05",
"brandName": "Harbour Street Bakery",
"companyId": "b8e1d0c4-2f3a-4c5d-9e6f-0a1b2c3d4e5f",
"isActive": true,
"defaultPlatform": "INSTAGRAM",
"generationPlatforms": [
"instagram"
],
"storyDestinations": [
"instagram_stories"
],
"socialProvider": "ZERNIO",
"channels": [
{
"platform": "instagram",
"displayName": "Harbour Street Bakery",
"username": "harbourstreetbakery",
"profileUrl": "https://www.instagram.com/harbourstreetbakery",
"health": "connected",
"healthReason": null,
"connectedAt": "2026-10-12T09:41:07.000Z",
"tokenRefreshInDays": 58,
"tokenExpiresAt": "2026-12-09T09:41:07.000Z",
"scopes": null,
"missingScopes": null,
"canPost": true,
"canFetchAnalytics": true
}
],
"readiness": {
"canGenerate": true,
"canPublish": true,
"blockers": []
},
"onboarding": {
"status": "ready"
}
}
}Readiness blockers
#readiness.blockers holds codes, not sentences. Branch on them, and treat the list as open: Native can add codes.
| Blocker | Means | What to do |
|---|---|---|
brand_inactive | The profile is inactive. | Nothing generates or publishes for it. |
no_brand_profile | The profile has no brand settings. | Nothing generates or publishes for it. Contact Native. |
profile_not_ready | New, in development. create_profile is still building the profile, or its build failed. onboarding says which. | Wait for the build, or retry it with create_profile and the same externalId. |
no_channel_connected | No publishing account is connected. | Send the client a connect link from create_connect_link. |
all_channels_need_reauth | Every publishing account needs reconnecting. | Send the client a connect link for each one. |
all_channels_disconnected | Every publishing account was removed on purpose. | There is nothing to reconnect. The client has to connect an account again. |
connection_check_failed | Native could not reach the social provider, so the connection state is unknown. | Call again later. Do not ask the client to connect: the accounts may be fine. |
Channel health
#| Health | Publishes | Means |
|---|---|---|
connected | Yes | The account works. |
expiring_soon | Yes | The token is due for refresh within 14 days. tokenRefreshInDays counts them down. Ask the client to reconnect before it reaches 0, or publishing will stop. |
needs_reauth | No | The client has to reconnect the account. |
disconnected | No | The account was removed. |
get_profile returns only the errors every tool can return. A brandId that does not exist, or is not yours, gets not_found or forbidden.
Find a client’s profile again
#Keep the brandId and companyId that create_profile returns next to your own externalId. Calling create_profile again with the same externalId gives them back too, but it is not a plain read: it restarts a failed build.
Two existing tools list what a token can reach. New, in development: with a partner token, they list only your own clients’ workspaces. That scoping arrives with partner access.
List workspaces
#get_workspacesPreviewInput
querystringOptionalOnly workspaces whose name or slug contains this text, ignoring case. 1 to 200 characters.
limitintegerOptionalHow many workspaces to return, from 1 to 200.
Default:
50
Output
workspacesobject[]RequiredThe workspaces that matched, up to
limit.idstringRequiredThe workspace’s id: a
companyId.namestringRequiredThe workspace’s name.
slugstringRequiredThe workspace’s slug.
logostring | nullRequiredA link to the workspace’s logo.
rolestringRequiredThe caller’s role in the workspace, such as
OWNER.membershipTypestring | nullRequiredThe workspace’s plan, such as
PRO.nullwhen it has none.numBrandsintegerRequiredHow many profiles the workspace holds.
totalCountintegerRequiredHow many workspaces matched, before
limit.truncatedbooleanRequiredWhether more workspaces matched than were returned.
needsUserChoicebooleanRequiredWhether more than one workspace matched. It tells an assistant to ask a person which one they mean. A backend can ignore it.
There is no cursor. A call returns at most 200 workspaces, and truncated says when more matched. With more clients than that, narrow with query, or look profiles up in your own records.
List a workspace’s profiles
#list_brandsPreviewInput
workspaceIdstringRequiredThe workspace’s id: the
companyIdfromcreate_profileorget_workspaces.
Output
workspaceIdstringRequiredThe workspace you asked about.
workspaceNamestring | nullRequiredThe workspace’s name.
brandsobject[]RequiredThe profiles in the workspace that the caller can reach.
idstringRequiredThe profile’s id: its
brandId.namestringRequiredThe profile’s name.
slugstringRequiredThe profile’s slug.
logostring | nullRequiredA link to the profile’s logo.
userRolestring | nullRequiredThe caller’s role on this profile, such as
ADMIN.isActivebooleanRequiredWhether the profile is active.
publishReadinessstringRequiredA quick verdict that costs no call to the social provider.
ready: it has a live publishing connection.not_connected: nothing can be scheduled until an account is connected.unknown: the provider holds the answer, so callget_profile.One of:
ready,not_connected,unknown
needsUserChoicebooleanRequiredWhether the workspace holds more than one profile. It tells an assistant to ask a person which one they mean. A client workspace
create_profilemade holds one, so it isfalsethere.
Both tools return only the errors every tool can return. list_brands answers forbidden for a workspace that is not yours.
End the service
#Two tools end the service for a client. Both run in the background. The first call schedules the work and answers at once. Calling again with the same brandId reports progress, so both are safe to repeat. Poll about every 30 seconds until the status is final. It usually takes a few minutes.
Both are partner only, and both need the mcp:write scope. As with create_profile, a token without partner access does not see them in tools/list, and calling one anyway fails as an unknown tool. Both refuse with stripe_billed_workspace when the workspace pays for its own Native subscription: that subscription has to be cancelled in Native.
Deactivate or delete
#| What | Deactivate | Delete |
|---|---|---|
| Scheduled posts | Withdrawn to unscheduled. Each keeps its date. | Erased with everything else. |
| Social accounts | Disconnected and removed at the provider. | Disconnected and removed at the provider first. |
| The workspace | Every profile in it is made inactive, and it loses its plan. | Erased, with every profile in it. |
| Posts, pictures and files | Kept. | Erased: posts, pictures, videos, uploaded files, brand settings and onboarding data. Image URLs stop resolving. |
| Undo | There is no reactivate tool. Reconnecting needs the client to log in with each platform again. | None. Native keeps no copy. |
| Use it when | The client stops, and may come back. | The client asks to be erased, such as a GDPR erasure request. |
Deactivate a profile
#deactivate_profileIn developmentEnds the service for a client. Every scheduled post is withdrawn to unscheduled, and keeps its date. The client’s social accounts are disconnected and removed at the provider. Every profile in the workspace is made inactive, and the workspace loses its plan, so no tool can generate, schedule or publish for it.
Nothing is deleted. But there is no reactivate tool, and reconnecting needs the client to log in with each platform again. Treat it as the end of the service.
Input
brandIdstringRequiredThe
brandIdcreate_profilereturned. Must be a UUID.
Output
companyIdstringRequiredThe workspace being deactivated.
statusstringRequireddeactivation_scheduleduntil the work has finished. Call again to poll.One of:
deactivation_scheduled,deactivatedunscheduledPostsnumberRequiredHow many scheduled posts have been withdrawn so far. Final once
statusisdeactivated.
First call
{
"data": {
"companyId": "b8e1d0c4-2f3a-4c5d-9e6f-0a1b2c3d4e5f",
"status": "deactivation_scheduled",
"unscheduledPosts": 0
}
}A few minutes later
{
"data": {
"companyId": "b8e1d0c4-2f3a-4c5d-9e6f-0a1b2c3d4e5f",
"status": "deactivated",
"unscheduledPosts": 4
}
}Errors
#| Code | Retryable | Meaning |
|---|---|---|
partner_only | false | The caller is not a partner. Nothing was changed. |
not_found | false | No profile has this brandId. |
forbidden | false | The profile is in a workspace you do not manage. |
stripe_billed_workspace | false | The workspace pays for its own Native subscription. Cancel it in Native. Nothing was changed. |
internal_error | true | Call again shortly. If Native recorded the request but could not start the work, the repeat starts it. |
Delete a profile
#delete_profileIn developmentPermanently erases a client: the whole workspace, every profile in it, its posts, pictures, videos and uploaded files, its brand settings and its onboarding data. Native disconnects the social accounts and removes them at the provider first. This is the call for a GDPR erasure request.
After the erasure, the client’s image URLs stop resolving.
Input
brandIdstringRequiredThe
brandIdcreate_profilereturned. Must be a UUID.confirmbooleanRequiredMust be
true. It says you know the deletion is permanent.One of:
true
Output
companyIdstringRequiredThe workspace being erased.
deletedbooleanRequiredAlways
true: the deletion is accepted and will happen.One of:
truestatusstringRequireddeletion_scheduleduntil the erasure has finished. Call again to poll. Once it readsdeleted, later calls keep answeringdeleted, nevernot_found.One of:
deletion_scheduled,deleted
Delete a profile and wait
import type { Client } from "@modelcontextprotocol/sdk/client/index.js";
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
// The first call schedules the erasure. Every later call reports progress.
async function deleteProfile(client: Client, brandId: string) {
for (;;) {
const result = await client.callTool(
{ name: "delete_profile", arguments: { brandId, confirm: true } },
undefined,
{ timeout: 150_000 },
);
const data = (result.structuredContent as { data: any } | undefined)?.data;
if (result.isError) {
if (!data.error.retryable) throw new Error("delete_profile failed: " + data.error.code);
} else if (data.status === "deleted") {
return data;
}
await sleep(30_000);
}
}First call
{
"data": {
"companyId": "b8e1d0c4-2f3a-4c5d-9e6f-0a1b2c3d4e5f",
"deleted": true,
"status": "deletion_scheduled"
}
}Errors
#| Code | Retryable | Meaning |
|---|---|---|
partner_only | false | The caller is not a partner. Nothing was changed. |
bad_request | false | confirm is not true. Through the MCP endpoint you get an SDK validation error instead: the input schema accepts only true, so the call is refused before it reaches Native. |
not_found | false | No profile has this brandId, and you have not deleted one with it before. |
forbidden | false | The profile is in a workspace you do not manage. |
stripe_billed_workspace | false | The workspace pays for its own Native subscription. Cancel it in Native first. Nothing was changed. |
internal_error | true | Call again shortly. If Native recorded the request but could not start the work, the repeat starts it. |
Errors every tool can return
#Any tool on this page can also return bad_request, not_found, forbidden, subscription_required, internal_error, upstream_timeout, upstream_unavailable, upstream_identity_failed, identity_provider_unavailable and upstream_<status>. The tools that change data can return writes_disabled. A partner token whose client Native has not set up gets partner_not_found on every call. With a partner token, any call can also get partner_downstream_auth_failed, which is retryable.
Branch on error.code, never on message, and respect retryable. An argument that breaks a tool’s input schema is refused by the MCP SDK before the tool runs, with a validation error instead of the error envelope. That covers an argument the tool does not take, and a brandId that is not a UUID on deactivate_profile or delete_profile. Errors explains the codes.
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.