Native APIGet started
PreviewNative API
MCP tools that let a partner run Native for its own clients from its backend: onboard, generate, approve and publish. Not live for partners yet.
Rather have your coding agent set it up? Give it the one line on Set up with an agent, and it adds the Native API to your project, ready for when your access opens.
What the Native API is for
#The Native API is for partners who run Native for their own clients, from their own backend. Your code onboards a client, generates posts, collects the client’s approval in your own interface, publishes, and handles what fails. No language model sits in between: your backend calls the tools directly.
Each client is a Native workspace of its own, with one brand. Every call names the client by its brandId, or by the id of a post that belongs to it. The tools also call a brand a profile: create_profile makes one and get_profile reads it.
Work for one client runs in this order:
- Onboard the client from its website, and wait while Native reads it. See Profiles.
- Set its look: colours, fonts and logo. See Branding.
- Connect its social accounts. The client logs in with each platform. See Social accounts.
- Generate posts from a brief, then edit them. See Generating posts and Editing posts.
- Collect the client’s approval of the exact content that will go out. See Approval.
- Schedule or publish, then track each destination. See Publishing.
- Offboard the client when the service ends, or erase it on request. See Profiles.
The flow walks through it call by call.
How it is shaped
#The Native API is a set of MCP tools, not REST endpoints. Your backend calls them over MCP’s Streamable HTTP transport at https://mcp.native.no/mcp.
- Every call is an MCP
tools/call. Use the official MCP SDK rather than writing JSON-RPC by hand. - The gateway is stateless. Each request stands on its own, and nothing is kept between calls.
tools/listreturns every tool with itsinputSchemaandoutputSchemaas JSON Schema. Check these pages against it. When the two disagree, the live list is right.- A few tools declare no fields in their output schema, only that the payload is an object. Their pages list what comes back.
- The output schema of a tool that changes data also allows
{ "cancelled": true, "changed": false }. That is the answer when a person declines a confirmation in an assistant. Your backend’s calls are never asked for one, so you do not get it. - Tools that change data need the
mcp:writescope as well asmcp:read. Each tool’sreadOnlyHintannotation says which kind it is. - Generation runs in the background.
create_postsanswers with abatchIdandregenerate_post_imagewith ajobId, and you poll until the work is done. There are no webhooks yet. - Three tools are for partners only:
create_profile,deactivate_profileanddelete_profile. They appear intools/listonly for a partner token.
What the badges mean
#| Badge | Meaning |
|---|---|
| Preview | The tool is in Native’s MCP gateway today and serves people signed in to Native. A partner can call it once partner sign-in is switched on for them. |
| In development | The tool, field or error code is built but not deployed. These pages show its planned shape, which can change before it ships. |
A Preview tool can have fields that are still in development. The description of each such field starts with “New, in development”.
Until partner sign-in ships, both badges mean the same thing for a partner: you can plan against the tool, but you cannot call it.
The tools
#| Tool | What it does | Status |
|---|---|---|
create_profile | Creates a client’s workspace and brand from its website. | In development |
get_profile | Shows a brand’s channels, their health, and whether it can generate and publish. | Preview |
get_workspaces | Lists the workspaces the token can reach. | Preview |
list_brands | Lists the brands in one workspace. | Preview |
deactivate_profile | Ends service for a client. Nothing is deleted. | In development |
delete_profile | Erases a client for good, for example on a GDPR request. | In development |
get_brand_style | Reads a brand’s colours, fonts and logos. | In development |
update_brand_style | Adds colours and sets colour roles and fonts. | In development |
upload_brand_logo | Saves a logo and, by default, makes it the one Native uses. | In development |
create_connect_link | Makes a single-use link the client opens to connect a social account. | Preview |
get_connect_options | Lists the Pages, organisations, boards or locations a connect can bind to. | In development |
complete_connect | Finishes a connect with the option the client chose. | In development |
create_posts | Generates 1 to 20 draft posts from one brief, in the background. | Preview |
get_post_batch_status | Reads how a batch is going, and the posts it made. | Preview |
view_post | Reads one post exactly as it would publish. | Preview |
update_post | Changes the copy, first comment, time or destinations of a post. | Preview |
generate_caption | Rewrites a caption in the brand’s voice. | Preview |
regenerate_post_image | Renders one picture of a post again, in the background. | Preview |
get_generation_job | Reports how a picture render ended. | Preview |
approve_post | Records approval of a post’s exact content. | Preview |
reject_post | Records a rejection with a reason. | Preview |
schedule_post | Books a post, or some of its destinations, for a time. | Preview |
reschedule_posts | Books or moves up to 25 posts in one call. | Preview |
publish_post_now | Publishes now, or retries the destinations that failed. | Preview |
unschedule_platform | Withdraws one destination from its booking. | Preview |
get_post_status | Reports the state of each destination of a post. | Preview |
The gateway has other tools, such as create_draft, delete_post, generate_image and upload_image. These pages do not cover them yet, and tools/list has their schemas.
What is still in development
#Partners cannot call the API until the first item below ships. The rest follow it. Native has not set dates for any of them, and will tell partners on the waiting list as each one goes live.
| Feature | What it gives you |
|---|---|
| Partner sign-in | Client credentials for your backend, and the codes partner_only and partner_not_found. |
| Approval digests | contentDigest and approval on post status, drafts and batch items, and postId and appUrl on review decisions. |
| Dry runs | dryRun on schedule_post and publish_post_now. |
| A versioned endpoint | /mcp/v1, which keeps its contract while newer versions ship. |
| Connecting accounts from your own app | platform and returnUrl on create_connect_link, plus get_connect_options and complete_connect. |
| Brand settings | get_brand_style, update_brand_style and upload_brand_logo. |
| Ending a client | deactivate_profile and delete_profile. |
| Approved content stays as approved | The codes approval_required and design_changed_after_approval. |
| Text on pictures | slideText on each picture, and rendering from design templates. |
| Alt text | altText on each picture. |
| Creating clients | create_profile, onboarding on get_profile, and the code profile_not_ready. |
Calling conventions
#Calling a tool
#Every call is an MCP tools/call over Streamable HTTP, with a bearer token in the Authorization header. Authentication shows how to get the token. With the token in hand, a call looks like this:
Calling get_profile
import { Client } from '@modelcontextprotocol/sdk/client/index.js'
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'
// token comes from accessToken() on /docs/authentication, brandId from create_profile.
const client = new Client({ name: 'partner-backend', version: '1.0.0' })
await client.connect(
new StreamableHTTPClientTransport(new URL('https://mcp.native.no/mcp'), {
requestInit: { headers: { Authorization: `Bearer ${token}` } },
}),
)
const result = await client.callTool(
{ name: 'get_profile', arguments: { brandId } },
undefined,
{ timeout: 150_000 }, // some writes take up to about 120 s
)
const data = (result.structuredContent as { data: any } | undefined)?.data
if (result.isError) {
// data is the error envelope: { ok: false, error: { code, message, retryable } }.
// A call that breaks the input schema has no envelope, only text in result.content.
console.error(data?.error?.code ?? result.content, data?.error?.retryable)
} else {
// data is the tool's payload
console.log(data.readiness)
}The token is set when the transport is created, so make a new client when you fetch a new token. Raise the SDK’s request timeout as shown. Otherwise a slow write can time out on your side after it has already succeeded on Native’s. Limits has the numbers.
Success
#On success, result.structuredContent is always { "data": <payload> }, where the payload is the object the tool’s page describes. result.content[0].text holds the same payload as a JSON string, and isError is absent.
structuredContent from get_profile, shortened
{
"data": {
"brandId": "3f6b1c2e-8d4a-4f7e-9b1a-2c5d8e0f4a71",
"brandName": "Harbour Bakery",
"isActive": true,
"readiness": {
"canGenerate": true,
"canPublish": false,
"blockers": [
"no_channel_connected"
]
}
}
}Failure
#When a tool fails, the result has isError: true, and structuredContent is { "data": <envelope> }. Here schedule_post found a caption too long for Instagram before it contacted any platform:
structuredContent from schedule_post, message shortened
{
"data": {
"ok": false,
"error": {
"code": "schedule_preflight_failed",
"message": "Nothing was scheduled: at least one destination will not publish this post as it stands.",
"retryable": false,
"blockedDestinations": [
{
"platform": "INSTAGRAM",
"code": "caption_too_long",
"characterCount": 2410,
"characterLimit": 2200
}
]
}
}
}The error envelope
okbooleanRequiredAlways
false.errorobjectRequiredWhat went wrong.
codestringRequiredWhat failed, as a stable code. Branch on this.
messagestringRequiredA sentence Native writes for people. It can change at any time, so show it or log it, but never parse it.
retryablebooleanRequiredWhether the same call, unchanged, could succeed.
falsemeans something has to change first.
- Some codes add fields of their own next to
code, such asinvalidFields,blockedDestinations,connectedPlatforms,resetDateorjobId. Errors lists each one with the code that uses it. - A code the gateway does not recognise arrives as
upstream_<status>, with the HTTP status Native’s backend answered, for exampleupstream_409. A 4xx is not retryable. A 429 or a 5xx is. - An argument that breaks a tool’s input schema is refused by the MCP SDK before the tool runs. The result has
isError: trueand the SDK’s validation message as text, with no envelope. This includes an argument the tool does not take: every tool on these pages refuses unknown arguments. Treat it as a bug in your call. - A missing, expired or invalid token is refused at the HTTP level, before any tool runs, so it never gets this envelope. See Authentication.
Ids
#brandId, post ids, batchId, jobId and platformPostId are opaque strings. Store them as you get them, and do not build or parse them. A few tools, create_connect_link among them, also refuse a brandId that is not a UUID.
Tools that act on a post take its id as id (for example view_post, approve_post and schedule_post) or as postId (for example generate_caption and regenerate_post_image). Each tool’s page says which. view_post and get_post_status return the id as both id and postId, with the same value.
Destination names
#A post’s destinations are uppercase names from Native’s platform list, such as INSTAGRAM, FACEBOOK or INSTAGRAM_STORIES. You see them in platforms, destinations[].platform and platformResults[].platform.
get_profile is the exception. Its channels[].platform and storyDestinations use lowercase channel names, such as instagram and instagram_stories. Stories use the base Instagram or Facebook connection, so they never appear as channels of their own. The platform input of create_connect_link, which is in development, takes lowercase names too.
Times
#Send times as ISO 8601 with Z or a numeric offset, for example 2026-11-02T09:00:00+01:00. The gateway converts them to UTC.
Retrying writes
#The gateway attaches an idempotency key to every write it makes to Native, but each tool call gets a fresh one. If you send the same call again, it is a new attempt and is not deduplicated: a repeated create_posts makes a second batch. Idempotency keys of your own are planned, not available.
So after a timeout or a dropped connection on a write, read the current state before you try again, with get_post_status, get_post_batch_status or view_post. A lost response does not prove that nothing changed. Errors has the retry rules.
Fields and codes you do not know
#Results can carry more fields than these pages list. New output fields, error codes and enum values can also appear at any time. Ignore the fields you do not use, and handle a code you do not know by its retryable flag.
Versions
#https://mcp.native.no/mcp always serves the latest version of the tools. A pinned endpoint, https://mcp.native.no/mcp/v1, is in development. Once it ships, partners should call it instead. The same token works on both, so switching versions changes only the URL.
With versioning, the initialize result reports the version you are on, as capabilities.experimental["no.native/partner-api"] with version and latestVersion. When latestVersion is greater than version, a newer contract has shipped.
The policy that comes with it: Native announces a breaking change at least two weeks before it ships, and the previous version keeps working for at least four weeks after. The announcement gives the date the old version shuts down. After that date, its path answers HTTP 404.
Breaking changes are: removing or renaming a tool or a field, tightening an input, changing what a field or code means, removing an enum value from output, changing which error code a failure returns, and changing a default. New tools, new optional inputs, new output fields, new enum values in output and new codes for new failures are not breaking, and can ship at any time.
What is not available yet
#- Partner sign-in. Machine-to-machine access is in development. Until it ships, a partner can call no tool at all.
- Webhooks. They are planned for finished generation, approval changes, publish results and disconnected accounts. Until then, poll.
- Idempotency keys of your own. Resending a write is a new attempt, so read the current state before you retry.
- Usage per client. A monthly usage report for each client is planned.
Next steps
#- FlowThe calls for one client, from onboarding to a published post.
- ProfilesCreate a client from its website, check what it can do, and offboard it.
- BrandingRead and set a brand’s colours, fonts and logo.
- Social accountsLet a client connect Instagram, Facebook and its other accounts.
- Generating postsTurn a brief into draft posts, with copy for each destination.
- Editing postsChange copy, destinations and pictures before the client reviews them.
- ApprovalRecord the client’s decision on the exact content that will go out.
- PublishingSchedule, publish now, and track each destination.
- ErrorsEvery error code, what it means, and whether to retry.
- LimitsBatch sizes, timeouts, upload sizes and the plan’s allowance.
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.