Native API· Branding

Native APITools

Preview

Branding

Read a brand’s colours, fonts and logos, and change them. A change applies to new posts only: nothing already made is drawn again.

A brand’s look is what Native draws its posts with: a palette of colours, which colour plays which role, a heading font, a body font and a logo. The tools on this page read that look and change it, one brand at a time.

They use the same code as the Design style settings in the Native app. A change you make here is the change a person would make there.

When Native builds a profile from a website, it reads the look from the site. So read the style first with get_brand_style, then change only what is wrong.

Existing posts keep their look

#

Nothing on this page draws an existing post again. That includes approved and scheduled posts. Only posts made after the change carry the new colours, fonts or logo.

This is on purpose: a post should publish the way it was approved.

Design style

#

Colour roles, the heading and body fonts, the logo setting for posts and the style guide all come from the brand’s Design style. Each save makes a new version of it. Older versions are kept, and a change can be undone from Design style in the Native app.

designStyle in get_brand_style says where a brand stands:

designStyleWhat it meansWhat you can change
activeThe brand has a Design style.Everything on this page.
not_set_upThere is no Design style version yet. colorRoles and styleGuide are null, and so are the heading and body fonts.Everything. The first update_brand_style that sets a role or a font creates the first version.
disabledThe brand is not on Design style. Its posts are drawn from the plain palette.Palette colours and logos. Roles and fonts are refused with design_style_unavailable.
unreadableThe brand has a Design style, but Native cannot read it.Palette colours and logos. Roles and fonts are refused with design_style_unavailable.

Colour roles

#

Each role holds one colour from the palette, or auto. With auto, Native picks a colour from the palette for each post.

RoleUsed forAccepts
backgroundThe canvas behind a post.A #RRGGBB hex or auto.
accentHighlights and buttons.A #RRGGBB hex, auto or none.
textThe text on a post. With auto, Native picks dark or light text by contrast with the background.A #RRGGBB hex or auto.
secondarySupporting details.A #RRGGBB hex, auto or none.

none means the brand has no colour in that role. Only accent and secondary can be empty.

Read a brand’s style

#
TOOLget_brand_styleIn development

Returns the brand’s palette, its colour roles, its fonts and font library, its logos, how the logo goes on posts, and a summary of its Design style. Reading changes nothing.

Arguments

  • brandIdstringRequired

    The brand, as a UUID.

Result data

  • brandIdstringRequired

    The brand.

  • brandNamestringRequired

    The brand’s name.

  • designStylestringRequired

    Whether the brand has a Design style. See Design style.

    One of: active, not_set_up, disabled, unreadable

  • colorsobject[]Required

    The palette, oldest colour first, each with the roles it plays.

    • idstringRequired

      The colour’s id.

    • hexstringRequired

      The colour as a hex code, such as #B5532A.

    • namestring | nullRequired

      The colour’s name, when it has one.

    • rolesstring[]Required

      The roles this colour plays. Empty when it plays none.

      One of: background, accent, text, secondary

  • colorRolesobject | nullRequired

    Which colour plays which role. null unless designStyle is active.

    • backgroundobject | stringRequired

      The colour as { hex, name }, or auto when Native picks one for each post.

    • accentobject | stringRequired

      The colour as { hex, name }, auto, or none for no accent.

    • textobject | stringRequired

      The colour as { hex, name }, or auto.

    • secondaryobject | stringRequired

      The colour as { hex, name }, auto, or none for no secondary colour.

  • fontsobjectRequired

    The fonts posts are set in, and the library they come from.

    • headingstring | nullRequired

      The heading font family, or null when none is set.

    • bodystring | nullRequired

      The body font family, or null when none is set.

    • libraryobject[]Required

      Every font family the brand has. A role can name any of them.

      • idstringRequired

        The family’s id.

      • namestringRequired

        The family’s name, such as Playfair Display.

      • typestring | nullRequired

        Where the family comes from, such as GOOGLE or CUSTOM.

  • logosobject[]Required

    The brand’s saved logos. The primary logo comes first, then the newest.

    • idstringRequired

      The logo’s id.

    • urlstringRequired

      A public URL of the file. It does not expire.

    • filenamestringRequired

      The file name.

    • descriptionstring | nullRequired

      What the logo is for, such as "For dark backgrounds".

    • isPrimarybooleanRequired

      true for the logo Native uses in images and ads.

  • primaryLogoUrlstring | nullRequired

    The logo Native puts in images and ads: the primary saved logo, or else the logo it read from the website. null when the brand has no logo.

  • logoOnPostsobjectRequired

    How the logo goes on posts.

    • frequencystringRequired

      How often a post carries the logo. Always never when the brand has no logo.

      One of: always, sometimes, never

    • placementstringRequired

      Where the logo goes. auto lets Native find a place on each post. The fixed places today are top-left, top-center, top-right, bottom-left, bottom-center and bottom-right.

    • logoIdstring | nullRequired

      The id of the saved logo the Design style picked for posts. It can be the primary logo itself. null when no file is picked and posts follow whichever logo is primary.

  • styleGuideobject | nullRequired

    A summary of the Design style, as Native gives it to the models that draw posts. null unless designStyle is active.

    • styleTypestring | nullRequired

      The kind of picture the brand’s posts use, such as illustration, template or typography. null when Native chooses for each post.

    • moodTagsstring[]Required

      Words for the mood of the brand’s posts.

    • aestheticTagsstring[]Required

      Words for their look.

    • personalityNotestringRequired

      A free note on the brand’s personality.

    • styleNotestring | nullRequired

      A free note on the visual style, when there is one.

    • rulesstring[]Required

      The brand’s design rules, word for word.

Result data

{
  "brandId": "3f6c2a9e-8b1d-4c57-9e0a-2d4b7f1c8a63",
  "brandName": "Harbour Street Bakery",
  "designStyle": "active",
  "colors": [
    {
      "id": "6a1f0c3e-2b7d-4d95-8e41-5c9a7b2d0f18",
      "hex": "#F4EDE1",
      "name": "Flour cream",
      "roles": [
        "background"
      ]
    },
    {
      "id": "9c4e2a71-0b3f-4f6d-a8e5-7d1b3c9f2e46",
      "hex": "#B5532A",
      "name": "Crust",
      "roles": [
        "accent"
      ]
    },
    {
      "id": "e2b8d5f4-7a19-4c03-9f6e-1a2d4b8c7e93",
      "hex": "#2B2B2B",
      "name": "Charcoal",
      "roles": [
        "text"
      ]
    }
  ],
  "colorRoles": {
    "background": {
      "hex": "#F4EDE1",
      "name": "Flour cream"
    },
    "accent": {
      "hex": "#B5532A",
      "name": "Crust"
    },
    "text": {
      "hex": "#2B2B2B",
      "name": "Charcoal"
    },
    "secondary": "auto"
  },
  "fonts": {
    "heading": "Playfair Display",
    "body": "Inter",
    "library": [
      {
        "id": "1b7e4c2d-9a3f-4e51-b6d8-0c2f7a9e3d15",
        "name": "Playfair Display",
        "type": "GOOGLE"
      },
      {
        "id": "8d2a6f1c-4b9e-4a07-93c5-e7f1b2d4a6c8",
        "name": "Inter",
        "type": "GOOGLE"
      }
    ]
  },
  "logos": [
    {
      "id": "5e9c1a7b-2d4f-4b83-a6e0-3f8b1c5d7a29",
      "url": "https://storage.googleapis.com/example-bucket/logos/harbour-street-logo.png",
      "filename": "harbour-street-logo.png",
      "description": null,
      "isPrimary": true
    }
  ],
  "primaryLogoUrl": "https://storage.googleapis.com/example-bucket/logos/harbour-street-logo.png",
  "logoOnPosts": {
    "frequency": "sometimes",
    "placement": "auto",
    "logoId": null
  },
  "styleGuide": {
    "styleType": "template",
    "moodTags": [
      "warm",
      "homely"
    ],
    "aestheticTags": [
      "natural light",
      "hand lettering"
    ],
    "personalityNote": "A neighbourhood bakery that talks like the person behind the counter.",
    "styleNote": null,
    "rules": [
      "Show bread on wooden boards, never on white plates."
    ]
  }
}

Errors: only the common ones. See Errors.

Change colours and fonts

#
TOOLupdate_brand_styleIn development

Adds colours to the palette, sets which colour plays which role, and sets the heading and body fonts. It changes data, so the token needs the mcp:write scope.

  • Send only what changes. Everything you leave out stays as it is. A call must name at least one palette colour, role or font.
  • palette only adds colours. It never removes or renames one. A hex the palette already has keeps its own name.
  • A role set to a hex the palette does not have adds that hex to the palette.
  • A font can be any family in the brand’s font library, or any Google Fonts family. A Google family the library does not have is added to it first, under Google’s own spelling.
  • Roles and fonts from one call are saved together, as one new Design style version.
  • It replaces the colours and fonts the brand had in those roles. If your client chose them, check with them before you change them.

Arguments

  • brandIdstringRequired

    The brand, as a UUID.

  • paletteobject[]Optional

    Colours to add to the palette. Up to 20.

    • hexstringRequired

      A six-digit hex code with its #, such as #6E8B74.

    • namestringOptional

      A name for the colour. Up to 100 characters. Ignored when the palette already has the hex.

  • colorsobjectOptional

    The roles to change. Send only the roles that change.

    • backgroundstringOptional

      A #RRGGBB hex, or auto.

    • accentstringOptional

      A #RRGGBB hex, auto, or none for no accent.

    • textstringOptional

      A #RRGGBB hex, or auto for dark or light text by contrast with the background.

    • secondarystringOptional

      A #RRGGBB hex, auto, or none for no secondary colour.

  • fontsobjectOptional

    The fonts to change.

    • headingstringOptional

      The heading font family. 1 to 200 characters. A family in fonts.library, or a Google Fonts family.

    • bodystringOptional

      The body font family. 1 to 200 characters. A family in fonts.library, or a Google Fonts family.

The result data is the brand’s style as it now reads, in the same shape as get_brand_style, with one more field:

Result data, added to the style

  • updatedobjectRequired

    What this call changed.

    • colorsAddedstring[]Required

      The hex codes this call added to the palette. Empty when it added none.

    • fontsAddedstring[]Required

      The font families this call added to the library from Google Fonts.

    • designStylestringRequired

      saved: a new Design style version was saved. unchanged: the roles and fonts you sent were already set, so no version was saved. not_requested: the call set no role and no font.

      One of: saved, unchanged, not_requested

This call adds a sage green to the palette, makes it the secondary colour, and sets a new heading font from Google Fonts:

Arguments

{
  "brandId": "3f6c2a9e-8b1d-4c57-9e0a-2d4b7f1c8a63",
  "palette": [
    {
      "hex": "#6E8B74",
      "name": "Sage"
    }
  ],
  "colors": {
    "secondary": "#6E8B74"
  },
  "fonts": {
    "heading": "Fraunces"
  }
}

Part of the result data

{
  "updated": {
    "colorsAdded": [
      "#6E8B74"
    ],
    "fontsAdded": [
      "Fraunces"
    ],
    "designStyle": "saved"
  }
}

Errors

#
CoderetryableWhat it means
font_not_foundfalseA font you named is neither in the library nor a Google Fonts family. Nothing changed. invalidFields names fonts.heading, fonts.body or both. Use a name from fonts.library, or the family name as Google Fonts spells it.
design_style_unavailablefalseThe brand is not on Design style, or its Design style cannot be read, so it cannot take roles or fonts. Nothing changed. A call that sends only palette still works.
design_style_busytrueAnother save landed at the same moment, or Native was still setting the Design style up. Roles and fonts were not set. Palette colours and library fonts the call added stay. Call again with the same arguments in a few seconds.
bad_requestfalseThe call names no change at all, or a value is not one Native accepts. When a field is at fault, invalidFields names it.

If Native cannot reach Google Fonts to look up a font, the call fails with internal_error. The same call can succeed a little later.

Of the codes in the table, only design_style_busy is worth sending again unchanged. A short wait between tries is enough:

Retry a busy save

async function updateBrandStyle(args: Record<string, unknown>) {
  for (let attempt = 1; attempt <= 3; attempt++) {
    const result = await client.callTool({ name: "update_brand_style", arguments: args });
    const data = (result.structuredContent as { data: any }).data;
    if (!result.isError) return data;
    if (data.error.code !== "design_style_busy") throw new Error(data.error.code);
    await new Promise((resolve) => setTimeout(resolve, 3_000 * attempt));
  }
  throw new Error("design_style_busy");
}

const style = await updateBrandStyle({
  brandId: "3f6c2a9e-8b1d-4c57-9e0a-2d4b7f1c8a63",
  colors: { accent: "#B5532A", secondary: "none" },
});
console.log(style.updated.designStyle);
#
TOOLupload_brand_logoIn development

Saves a logo for the brand. By default it also becomes the primary logo: the one Native uses in images and ads. It changes data, so the token needs the mcp:write scope.

  • Send the file one of two ways: a public https URL in sourceUrl, which Native fetches, or the file itself in dataBase64 with its contentType. Send exactly one of them.
  • The logo it replaces stays among the brand’s logos.
  • Posts and ads already made keep the logo they were made with.
  • An SVG is stored as a PNG.

Arguments

  • brandIdstringRequired

    The brand, as a UUID.

  • sourceUrlstringOptional

    A public https URL of the logo file. Native fetches it. Up to 2,048 characters. Send this or dataBase64, not both.

  • dataBase64stringOptional

    The file as standard base64, with +, / and = padding. Line breaks are fine. base64url is refused. Up to 11 MiB of characters, which is an image of about 8 MiB. Needs contentType.

  • contentTypestringOptional

    Required with dataBase64. The bytes must be the type you name.

    One of: image/png, image/jpeg, image/webp, image/gif, image/svg+xml

  • filenamestringOptional

    A name for the file. Up to 120 characters. Native gives it the extension of the stored file. logo when you leave it out.

  • descriptionstringOptional

    What this logo is for, such as "For dark backgrounds". Up to 1,000 characters.

  • primarybooleanOptional

    Make this the logo Native uses in images and ads. false saves an extra variant and leaves the primary logo as it is. If the brand has no primary logo yet, the new logo becomes primary even with false.

    Default: true

Result data

  • brandIdstringRequired

    The brand.

  • logoobjectRequired

    The saved logo, in the same shape as logos[] in get_brand_style.

    • idstringRequired

      The logo’s id.

    • urlstringRequired

      A public URL of the stored file. It does not expire.

    • filenamestringRequired

      The file name.

    • descriptionstring | nullRequired

      The description you sent, or null.

    • isPrimarybooleanRequired

      Whether this is now the primary logo.

  • madePrimarybooleanRequired

    Whether this call made the logo the primary one.

  • primaryLogoUrlstring | nullRequired

    The logo Native now puts in images and ads.

  • logoOnPostsobjectRequired

    How the logo goes on posts, in the same shape as logoOnPosts in get_brand_style.

    • frequencystringRequired

      How often a post carries the logo.

      One of: always, sometimes, never

    • placementstringRequired

      Where the logo goes, such as auto or bottom-right.

    • logoIdstring | nullRequired

      The saved logo the Design style picked for posts, or null when posts follow the primary logo.

Arguments

{
  "brandId": "3f6c2a9e-8b1d-4c57-9e0a-2d4b7f1c8a63",
  "sourceUrl": "https://www.harbourstreetbakery.example/assets/logo-light.svg",
  "description": "For dark backgrounds",
  "primary": false
}

Result data

{
  "brandId": "3f6c2a9e-8b1d-4c57-9e0a-2d4b7f1c8a63",
  "logo": {
    "id": "b3d7f2a9-6c1e-4f08-8a5d-2e9c4b7f1a63",
    "url": "https://storage.googleapis.com/example-bucket/logos/logo.png",
    "filename": "logo.png",
    "description": "For dark backgrounds",
    "isPrimary": false
  },
  "madePrimary": false,
  "primaryLogoUrl": "https://storage.googleapis.com/example-bucket/logos/harbour-street-logo.png",
  "logoOnPosts": {
    "frequency": "sometimes",
    "placement": "auto",
    "logoId": null
  }
}

To send the file itself, read it and encode it as base64:

Upload a logo from a file

import { readFile } from "node:fs/promises";

const bytes = await readFile("harbour-street-logo.png");

const result = await client.callTool({
  name: "upload_brand_logo",
  arguments: {
    brandId: "3f6c2a9e-8b1d-4c57-9e0a-2d4b7f1c8a63",
    dataBase64: bytes.toString("base64"),
    contentType: "image/png",
    filename: "harbour-street-logo.png",
  },
});

const data = (result.structuredContent as { data: any }).data;
if (result.isError) {
  // "logo_unreadable" when the file is not a usable image
  throw new Error(data.error.code);
}
if (data.logoOnPosts.logoId && data.logoOnPosts.logoId !== data.logo.id) {
  // Posts keep the logo the Design style picked for them.
}

Errors

#
CoderetryableWhat it means
logo_unreadablefalseThe file is not a usable logo. It is not an image at least 16 px each way, it failed the upload scan, or it could not be fetched from sourceUrl. A download that fails, answers with something other than an image, or redirects to a private address counts as not fetched. Nothing was saved. invalidFields names sourceUrl or dataBase64.
source_not_httpsfalsesourceUrl is not https. Nothing was fetched. Send the same address as https.
source_not_publicfalsesourceUrl itself points at a private or internal address. Nothing was fetched. Use an address anyone on the internet can reach.
bad_requestfalsecontentType is not one of the five types, dataBase64 is not valid base64 or is empty, the decoded file is over 8 MiB, or the bytes are not the type contentType names.

If Native cannot scan or store the file, the call fails with internal_error and nothing is saved. The same call can then succeed.

Other errors

#

Every tool on this page can also return the errors any tool can: bad_request, not_found, forbidden, subscription_required, internal_error, upstream_timeout, upstream_unavailable, upstream_identity_failed, identity_provider_unavailable and upstream_<status>. update_brand_style and upload_brand_logo can return writes_disabled. Errors explains each one.

The Native API is not open yet

Partner access is being built now. Tell us what you would build, and we will be in touch when it opens.

Contact sales