Native API· Profiles

Native APITools

Preview

Profiles

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

#
StepCallWhat happens
Createcreate_profileNative answers at once with a brandId and starts building the profile from the website.
Waitget_profilePoll about once a minute until onboarding.status is ready or failed. It usually takes 10 to 30 minutes.
Useget_profileCheck readiness before you generate or schedule. Check channel health once a day.
Enddeactivate_profile or delete_profileNative 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

#
TOOLcreate_profileIn development

Creates 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

  • websiteUrlstringRequired

    The client’s own website, such as https://harbourstreetbakery.example. A bare domain is accepted. Not a social media profile. 1 to 2,048 characters.

  • namestringRequired

    The client’s business name. Native uses it for the workspace and its profile. 1 to 100 characters.

  • externalIdstringRequired

    Your 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.

  • allowTeamPhotosbooleanOptional

    Lets 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

  • brandIdstringRequired

    The profile’s id. Every other tool takes it.

  • companyIdstringRequired

    The id of the client’s workspace.

  • statusstringRequired

    building on a new profile, and after a repeat that restarts a failed build. On any other repeat, the build’s current state. failed here means Native could not start the build.

    One of: building, ready, failed

  • errorstringOptional

    Present only when status is failed. One of the build failure codes, in practice build_not_queued. Treat it as an open set.

  • createdbooleanRequired

    false when the call answered with the profile an earlier call with the same externalId made.

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

#
CodeRetryableMeaning
partner_onlyfalseNative refused the call because the caller is not a partner. Nothing was created.
invalid_website_urlfalseNative refused the website before creating anything. websiteRefusal says why. Send the client’s own website.
bad_requestfalseNative 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.

CodeMeaning
BLOCKED_DOMAINA social media or platform address, not the client’s own site.
URL_NOT_REACHABLEThe address does not resolve, or it refused the connection.
URL_UNSAFEThe address is flagged as harmful.
URL_NOT_ALLOWEDThe 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.

CodeMeaning
website_unreachableNothing could read the website. A retry helps only once the site answers again.
analysis_failedNative read the website, but the analysis of it failed.
enrichment_failedNative could not fill in the profile from the analysis.
build_not_queuedNative could not start the next step of the build.
build_interruptedThe 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

#
TOOLget_profilePreview

Shows 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

  • brandIdstringRequired

    The profile’s id.

  • includeScopesbooleanOptional

    Also 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

  • brandIdstringRequired

    The profile’s id.

  • brandNamestringRequired

    The profile’s name.

  • companyIdstringRequired

    The workspace the profile belongs to.

  • isActivebooleanRequired

    Whether the profile is active. An inactive profile cannot generate or publish.

  • defaultPlatformstring | nullRequired

    Where a post goes when a call names no destinations, as an uppercase destination name such as INSTAGRAM. null when none is set.

  • generationPlatformsstring[]Required

    The channels the profile can publish to right now, in lowercase, such as instagram. A channel that needs reconnecting is left out.

  • storyDestinationsstring[] | nullOptional

    The 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_stories

  • socialProviderstringRequired

    The provider Native publishes through for this profile, such as ZERNIO.

  • channelsobject[]Required

    The connected social accounts, one per channel.

    • platformstringRequired

      The channel, in lowercase, such as instagram.

    • displayNamestring | nullRequired

      The account’s display name.

    • usernamestring | nullRequired

      The account’s username.

    • profileUrlstring | nullRequired

      A link to the account.

    • healthstringRequired

      Whether the account can publish. See Channel health.

      One of: connected, expiring_soon, needs_reauth, disconnected

    • healthReasonstring | nullRequired

      A code saying why health is not connected, such as token_expired_or_revoked or token_refresh_due. null when it is connected. Treat it as an open set.

    • connectedAtISO 8601 string | nullRequired

      When the account was connected.

    • tokenRefreshInDaysnumber | nullRequired

      Days until the account’s token has to be refreshed. null means the provider does not report it, not that it never expires.

    • tokenExpiresAtISO 8601 string | nullOptional

      When the account’s token expires, as the provider reports it. null means not reported. May be absent.

    • scopesstring[] | nullRequired

      The OAuth scopes the account granted. Filled only with includeScopes. null means unknown, not none.

    • missingScopesstring[] | nullOptional

      Scopes Native needs that the account has not granted. null when the scopes are unknown. May be absent.

    • canPostboolean | nullOptional

      The provider’s own verdict on whether the account can post. null when the provider gave none. May be absent.

    • canFetchAnalyticsboolean | nullOptional

      The provider’s own verdict on whether Native can read the account’s analytics. null when the provider gave none. May be absent.

  • readinessobjectRequired

    Whether the profile can generate and publish now, and what stands in the way.

    • canGeneratebooleanRequired

      Whether Native can generate posts for the profile now.

    • canPublishbooleanRequired

      Whether the profile can publish now: it is active, has brand settings and has at least one channel it can publish to.

    • blockersstring[]Required

      What stands in the way, as codes. Empty when nothing does. See Readiness blockers.

  • onboardingobjectOptional

    New, in development. How the build create_profile started is going. Only on a profile create_profile made, and absent for every other profile.

    • statusstringRequired

      building while Native reads the website and fills in the profile.

      One of: building, ready, failed

    • errorstringOptional

      Present only when status is failed. 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.

BlockerMeansWhat to do
brand_inactiveThe profile is inactive.Nothing generates or publishes for it.
no_brand_profileThe profile has no brand settings.Nothing generates or publishes for it. Contact Native.
profile_not_readyNew, 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_connectedNo publishing account is connected.Send the client a connect link from create_connect_link.
all_channels_need_reauthEvery publishing account needs reconnecting.Send the client a connect link for each one.
all_channels_disconnectedEvery publishing account was removed on purpose.There is nothing to reconnect. The client has to connect an account again.
connection_check_failedNative 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

#
HealthPublishesMeans
connectedYesThe account works.
expiring_soonYesThe 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_reauthNoThe client has to reconnect the account.
disconnectedNoThe 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

#
TOOLget_workspacesPreview

Input

  • querystringOptional

    Only workspaces whose name or slug contains this text, ignoring case. 1 to 200 characters.

  • limitintegerOptional

    How many workspaces to return, from 1 to 200.

    Default: 50

Output

  • workspacesobject[]Required

    The workspaces that matched, up to limit.

    • idstringRequired

      The workspace’s id: a companyId.

    • namestringRequired

      The workspace’s name.

    • slugstringRequired

      The workspace’s slug.

    • logostring | nullRequired

      A link to the workspace’s logo.

    • rolestringRequired

      The caller’s role in the workspace, such as OWNER.

    • membershipTypestring | nullRequired

      The workspace’s plan, such as PRO. null when it has none.

    • numBrandsintegerRequired

      How many profiles the workspace holds.

  • totalCountintegerRequired

    How many workspaces matched, before limit.

  • truncatedbooleanRequired

    Whether more workspaces matched than were returned.

  • needsUserChoicebooleanRequired

    Whether 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

#
TOOLlist_brandsPreview

Input

  • workspaceIdstringRequired

    The workspace’s id: the companyId from create_profile or get_workspaces.

Output

  • workspaceIdstringRequired

    The workspace you asked about.

  • workspaceNamestring | nullRequired

    The workspace’s name.

  • brandsobject[]Required

    The profiles in the workspace that the caller can reach.

    • idstringRequired

      The profile’s id: its brandId.

    • namestringRequired

      The profile’s name.

    • slugstringRequired

      The profile’s slug.

    • logostring | nullRequired

      A link to the profile’s logo.

    • userRolestring | nullRequired

      The caller’s role on this profile, such as ADMIN.

    • isActivebooleanRequired

      Whether the profile is active.

    • publishReadinessstringRequired

      A 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 call get_profile.

      One of: ready, not_connected, unknown

  • needsUserChoicebooleanRequired

    Whether the workspace holds more than one profile. It tells an assistant to ask a person which one they mean. A client workspace create_profile made holds one, so it is false there.

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

#
WhatDeactivateDelete
Scheduled postsWithdrawn to unscheduled. Each keeps its date.Erased with everything else.
Social accountsDisconnected and removed at the provider.Disconnected and removed at the provider first.
The workspaceEvery profile in it is made inactive, and it loses its plan.Erased, with every profile in it.
Posts, pictures and filesKept.Erased: posts, pictures, videos, uploaded files, brand settings and onboarding data. Image URLs stop resolving.
UndoThere is no reactivate tool. Reconnecting needs the client to log in with each platform again.None. Native keeps no copy.
Use it whenThe client stops, and may come back.The client asks to be erased, such as a GDPR erasure request.

Deactivate a profile

#
TOOLdeactivate_profileIn development

Ends 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

  • brandIdstringRequired

    The brandId create_profile returned. Must be a UUID.

Output

  • companyIdstringRequired

    The workspace being deactivated.

  • statusstringRequired

    deactivation_scheduled until the work has finished. Call again to poll.

    One of: deactivation_scheduled, deactivated

  • unscheduledPostsnumberRequired

    How many scheduled posts have been withdrawn so far. Final once status is deactivated.

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

#
CodeRetryableMeaning
partner_onlyfalseThe caller is not a partner. Nothing was changed.
not_foundfalseNo profile has this brandId.
forbiddenfalseThe profile is in a workspace you do not manage.
stripe_billed_workspacefalseThe workspace pays for its own Native subscription. Cancel it in Native. Nothing was changed.
internal_errortrueCall again shortly. If Native recorded the request but could not start the work, the repeat starts it.

Delete a profile

#
TOOLdelete_profileIn development

Permanently 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

  • brandIdstringRequired

    The brandId create_profile returned. Must be a UUID.

  • confirmbooleanRequired

    Must be true. It says you know the deletion is permanent.

    One of: true

Output

  • companyIdstringRequired

    The workspace being erased.

  • deletedbooleanRequired

    Always true: the deletion is accepted and will happen.

    One of: true

  • statusstringRequired

    deletion_scheduled until the erasure has finished. Call again to poll. Once it reads deleted, later calls keep answering deleted, never not_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

#
CodeRetryableMeaning
partner_onlyfalseThe caller is not a partner. Nothing was changed.
bad_requestfalseconfirm 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_foundfalseNo profile has this brandId, and you have not deleted one with it before.
forbiddenfalseThe profile is in a workspace you do not manage.
stripe_billed_workspacefalseThe workspace pays for its own Native subscription. Cancel it in Native first. Nothing was changed.
internal_errortrueCall 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.

Contact sales