# Profiles Source: https://docs.native.no/native-api/profiles > **Preview.** The Native API is not live yet. This page describes the planned interface; names and fields can change before access opens. 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. > **Partner access is not live yet.** You cannot call these tools with a partner token yet. Partner sign-in with client credentials is still in development. `get_profile`, `list_brands` and `get_workspaces` serve signed-in Native users today. `create_profile`, `deactivate_profile`, `delete_profile` and the `onboarding` field are in development, so their shapes can still change. 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](https://docs.native.no/native-api/social-accounts) for the client’s social accounts while the profile builds. The whole sequence, from onboarding to publishing, is in [the flow](https://docs.native.no/native-api/flow). ## Create a profile MCP tool `create_profile` (in 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](https://docs.native.no/authentication)). Input: - `websiteUrl` (string, required): 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. - `name` (string, required): The client’s business name. Native uses it for the workspace and its profile. 1 to 100 characters. - `externalId` (string, required): 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](https://docs.native.no/native-api/profiles#calling-again). - `allowTeamPhotos` (boolean, optional; default: `false`): 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. Output: - `brandId` (string, required): The profile’s id. Every other tool takes it. - `companyId` (string, required): The id of the client’s workspace. - `status` (string, required; one of: `building`, `ready`, `failed`): `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. - `error` (string, optional): Present only when `status` is `failed`. One of the [build failure codes](https://docs.native.no/native-api/profiles#build-failures), in practice `build_not_queued`. Treat it as an open set. - `created` (boolean, required): `false` when the call answered with the profile an earlier call with the same `externalId` made. Create a profile: ```ts 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: ```json { "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: ```json { "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](https://docs.native.no/native-api/profiles#common-errors). ## 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: ```json { "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: ```ts 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 MCP tool `get_profile` (preview) 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: - `brandId` (string, required): The profile’s id. - `includeScopes` (boolean, optional; default: `false`): 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. Output: - `brandId` (string, required): The profile’s id. - `brandName` (string, required): The profile’s name. - `companyId` (string, required): The workspace the profile belongs to. - `isActive` (boolean, required): Whether the profile is active. An inactive profile cannot generate or publish. - `defaultPlatform` (string | null, required): Where a post goes when a call names no destinations, as an uppercase destination name such as `INSTAGRAM`. `null` when none is set. - `generationPlatforms` (string[], required): The channels the profile can publish to right now, in lowercase, such as `instagram`. A channel that needs reconnecting is left out. - `storyDestinations` (string[] | null, optional; one of: `instagram_stories`, `facebook_stories`): 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. - `socialProvider` (string, required): The provider Native publishes through for this profile, such as `ZERNIO`. - `channels` (object[], required): The connected social accounts, one per channel. - `platform` (string, required): The channel, in lowercase, such as `instagram`. - `displayName` (string | null, required): The account’s display name. - `username` (string | null, required): The account’s username. - `profileUrl` (string | null, required): A link to the account. - `health` (string, required; one of: `connected`, `expiring_soon`, `needs_reauth`, `disconnected`): Whether the account can publish. See [Channel health](https://docs.native.no/native-api/profiles#channel-health). - `healthReason` (string | null, required): 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. - `connectedAt` (ISO 8601 string | null, required): When the account was connected. - `tokenRefreshInDays` (number | null, required): Days until the account’s token has to be refreshed. `null` means the provider does not report it, not that it never expires. - `tokenExpiresAt` (ISO 8601 string | null, optional): When the account’s token expires, as the provider reports it. `null` means not reported. May be absent. - `scopes` (string[] | null, required): The OAuth scopes the account granted. Filled only with `includeScopes`. `null` means unknown, not none. - `missingScopes` (string[] | null, optional): Scopes Native needs that the account has not granted. `null` when the scopes are unknown. May be absent. - `canPost` (boolean | null, optional): The provider’s own verdict on whether the account can post. `null` when the provider gave none. May be absent. - `canFetchAnalytics` (boolean | null, optional): The provider’s own verdict on whether Native can read the account’s analytics. `null` when the provider gave none. May be absent. - `readiness` (object, required): Whether the profile can generate and publish now, and what stands in the way. - `canGenerate` (boolean, required): Whether Native can generate posts for the profile now. - `canPublish` (boolean, required): Whether the profile can publish now: it is active, has brand settings and has at least one channel it can publish to. - `blockers` (string[], required): What stands in the way, as codes. Empty when nothing does. See [Readiness blockers](https://docs.native.no/native-api/profiles#readiness-blockers). - `onboarding` (object, optional): New, in development. How the build `create_profile` started is going. Only on a profile `create_profile` made, and absent for every other profile. - `status` (string, required; one of: `building`, `ready`, `failed`): `building` while Native reads the website and fills in the profile. - `error` (string, optional): Present only when `status` is `failed`. One of the [build failure codes](https://docs.native.no/native-api/profiles#build-failures). 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: ```json { "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](https://docs.native.no/native-api/profiles#common-errors). 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 MCP tool `get_workspaces` (preview) Input: - `query` (string, optional): Only workspaces whose name or slug contains this text, ignoring case. 1 to 200 characters. - `limit` (integer, optional; default: `50`): How many workspaces to return, from 1 to 200. Output: - `workspaces` (object[], required): The workspaces that matched, up to `limit`. - `id` (string, required): The workspace’s id: a `companyId`. - `name` (string, required): The workspace’s name. - `slug` (string, required): The workspace’s slug. - `logo` (string | null, required): A link to the workspace’s logo. - `role` (string, required): The caller’s role in the workspace, such as `OWNER`. - `membershipType` (string | null, required): The workspace’s plan, such as `PRO`. `null` when it has none. - `numBrands` (integer, required): How many profiles the workspace holds. - `totalCount` (integer, required): How many workspaces matched, before `limit`. - `truncated` (boolean, required): Whether more workspaces matched than were returned. - `needsUserChoice` (boolean, required): 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 MCP tool `list_brands` (preview) Input: - `workspaceId` (string, required): The workspace’s id: the `companyId` from `create_profile` or `get_workspaces`. Output: - `workspaceId` (string, required): The workspace you asked about. - `workspaceName` (string | null, required): The workspace’s name. - `brands` (object[], required): The profiles in the workspace that the caller can reach. - `id` (string, required): The profile’s id: its `brandId`. - `name` (string, required): The profile’s name. - `slug` (string, required): The profile’s slug. - `logo` (string | null, required): A link to the profile’s logo. - `userRole` (string | null, required): The caller’s role on this profile, such as `ADMIN`. - `isActive` (boolean, required): Whether the profile is active. - `publishReadiness` (string, required; one of: `ready`, `not_connected`, `unknown`): 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`. - `needsUserChoice` (boolean, required): 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](https://docs.native.no/native-api/profiles#common-errors). `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 MCP tool `deactivate_profile` (in 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: - `brandId` (string, required): The `brandId` `create_profile` returned. Must be a UUID. Output: - `companyId` (string, required): The workspace being deactivated. - `status` (string, required; one of: `deactivation_scheduled`, `deactivated`): `deactivation_scheduled` until the work has finished. Call again to poll. - `unscheduledPosts` (number, required): How many scheduled posts have been withdrawn so far. Final once `status` is `deactivated`. First call: ```json { "data": { "companyId": "b8e1d0c4-2f3a-4c5d-9e6f-0a1b2c3d4e5f", "status": "deactivation_scheduled", "unscheduledPosts": 0 } } ``` A few minutes later: ```json { "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 MCP tool `delete_profile` (in development) > **This cannot be undone.** There is no restore, and Native keeps no copy. Call it only when the client has asked to be erased. Use `deactivate_profile` if the client may come back. 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: - `brandId` (string, required): The `brandId` `create_profile` returned. Must be a UUID. - `confirm` (boolean, required; one of: `true`): Must be `true`. It says you know the deletion is permanent. Output: - `companyId` (string, required): The workspace being erased. - `deleted` (boolean, required; one of: `true`): Always `true`: the deletion is accepted and will happen. - `status` (string, required; one of: `deletion_scheduled`, `deleted`): `deletion_scheduled` until the erasure has finished. Call again to poll. Once it reads `deleted`, later calls keep answering `deleted`, never `not_found`. Delete a profile and wait: ```ts 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: ```json { "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_`. 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](https://docs.native.no/native-api/errors) explains the codes.