Native APITools
PreviewBranding
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:
designStyle | What it means | What you can change |
|---|---|---|
active | The brand has a Design style. | Everything on this page. |
not_set_up | There 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. |
disabled | The 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. |
unreadable | The 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.
| Role | Used for | Accepts |
|---|---|---|
background | The canvas behind a post. | A #RRGGBB hex or auto. |
accent | Highlights and buttons. | A #RRGGBB hex, auto or none. |
text | The text on a post. With auto, Native picks dark or light text by contrast with the background. | A #RRGGBB hex or auto. |
secondary | Supporting 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
#get_brand_styleIn developmentReturns 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
brandIdstringRequiredThe brand, as a UUID.
Result data
brandIdstringRequiredThe brand.
brandNamestringRequiredThe brand’s name.
designStylestringRequiredWhether the brand has a Design style. See Design style.
One of:
active,not_set_up,disabled,unreadablecolorsobject[]RequiredThe palette, oldest colour first, each with the roles it plays.
idstringRequiredThe colour’s id.
hexstringRequiredThe colour as a hex code, such as
#B5532A.namestring | nullRequiredThe colour’s name, when it has one.
rolesstring[]RequiredThe roles this colour plays. Empty when it plays none.
One of:
background,accent,text,secondary
colorRolesobject | nullRequiredWhich colour plays which role.
nullunlessdesignStyleisactive.backgroundobject | stringRequiredThe colour as
{ hex, name }, orautowhen Native picks one for each post.accentobject | stringRequiredThe colour as
{ hex, name },auto, ornonefor no accent.textobject | stringRequiredThe colour as
{ hex, name }, orauto.secondaryobject | stringRequiredThe colour as
{ hex, name },auto, ornonefor no secondary colour.
fontsobjectRequiredThe fonts posts are set in, and the library they come from.
headingstring | nullRequiredThe heading font family, or
nullwhen none is set.bodystring | nullRequiredThe body font family, or
nullwhen none is set.libraryobject[]RequiredEvery font family the brand has. A role can name any of them.
idstringRequiredThe family’s id.
namestringRequiredThe family’s name, such as
Playfair Display.typestring | nullRequiredWhere the family comes from, such as
GOOGLEorCUSTOM.
logosobject[]RequiredThe brand’s saved logos. The primary logo comes first, then the newest.
idstringRequiredThe logo’s id.
urlstringRequiredA public URL of the file. It does not expire.
filenamestringRequiredThe file name.
descriptionstring | nullRequiredWhat the logo is for, such as "For dark backgrounds".
isPrimarybooleanRequiredtruefor the logo Native uses in images and ads.
primaryLogoUrlstring | nullRequiredThe logo Native puts in images and ads: the primary saved logo, or else the logo it read from the website.
nullwhen the brand has no logo.logoOnPostsobjectRequiredHow the logo goes on posts.
frequencystringRequiredHow often a post carries the logo. Always
neverwhen the brand has no logo.One of:
always,sometimes,neverplacementstringRequiredWhere the logo goes.
autolets Native find a place on each post. The fixed places today aretop-left,top-center,top-right,bottom-left,bottom-centerandbottom-right.logoIdstring | nullRequiredThe id of the saved logo the Design style picked for posts. It can be the primary logo itself.
nullwhen no file is picked and posts follow whichever logo is primary.
styleGuideobject | nullRequiredA summary of the Design style, as Native gives it to the models that draw posts.
nullunlessdesignStyleisactive.styleTypestring | nullRequiredThe kind of picture the brand’s posts use, such as
illustration,templateortypography.nullwhen Native chooses for each post.moodTagsstring[]RequiredWords for the mood of the brand’s posts.
aestheticTagsstring[]RequiredWords for their look.
personalityNotestringRequiredA free note on the brand’s personality.
styleNotestring | nullRequiredA free note on the visual style, when there is one.
rulesstring[]RequiredThe 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
#update_brand_styleIn developmentAdds 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.
paletteonly 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
brandIdstringRequiredThe brand, as a UUID.
paletteobject[]OptionalColours to add to the palette. Up to 20.
hexstringRequiredA six-digit hex code with its
#, such as#6E8B74.namestringOptionalA name for the colour. Up to 100 characters. Ignored when the palette already has the hex.
colorsobjectOptionalThe roles to change. Send only the roles that change.
backgroundstringOptionalA
#RRGGBBhex, orauto.accentstringOptionalA
#RRGGBBhex,auto, ornonefor no accent.textstringOptionalA
#RRGGBBhex, orautofor dark or light text by contrast with the background.secondarystringOptionalA
#RRGGBBhex,auto, ornonefor no secondary colour.
fontsobjectOptionalThe fonts to change.
headingstringOptionalThe heading font family. 1 to 200 characters. A family in
fonts.library, or a Google Fonts family.bodystringOptionalThe 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
updatedobjectRequiredWhat this call changed.
colorsAddedstring[]RequiredThe hex codes this call added to the palette. Empty when it added none.
fontsAddedstring[]RequiredThe font families this call added to the library from Google Fonts.
designStylestringRequiredsaved: 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
#| Code | retryable | What it means |
|---|---|---|
font_not_found | false | A 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_unavailable | false | The 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_busy | true | Another 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_request | false | The 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);Upload a logo
#upload_brand_logoIn developmentSaves 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 indataBase64with itscontentType. 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
brandIdstringRequiredThe brand, as a UUID.
sourceUrlstringOptionalA public https URL of the logo file. Native fetches it. Up to 2,048 characters. Send this or
dataBase64, not both.dataBase64stringOptionalThe 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. NeedscontentType.contentTypestringOptionalRequired with
dataBase64. The bytes must be the type you name.One of:
image/png,image/jpeg,image/webp,image/gif,image/svg+xmlfilenamestringOptionalA name for the file. Up to 120 characters. Native gives it the extension of the stored file.
logowhen you leave it out.descriptionstringOptionalWhat this logo is for, such as "For dark backgrounds". Up to 1,000 characters.
primarybooleanOptionalMake this the logo Native uses in images and ads.
falsesaves 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 withfalse.Default:
true
Result data
brandIdstringRequiredThe brand.
logoobjectRequiredThe saved logo, in the same shape as
logos[]inget_brand_style.idstringRequiredThe logo’s id.
urlstringRequiredA public URL of the stored file. It does not expire.
filenamestringRequiredThe file name.
descriptionstring | nullRequiredThe description you sent, or
null.isPrimarybooleanRequiredWhether this is now the primary logo.
madePrimarybooleanRequiredWhether this call made the logo the primary one.
primaryLogoUrlstring | nullRequiredThe logo Native now puts in images and ads.
logoOnPostsobjectRequiredHow the logo goes on posts, in the same shape as
logoOnPostsinget_brand_style.frequencystringRequiredHow often a post carries the logo.
One of:
always,sometimes,neverplacementstringRequiredWhere the logo goes, such as
autoorbottom-right.logoIdstring | nullRequiredThe saved logo the Design style picked for posts, or
nullwhen 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
#| Code | retryable | What it means |
|---|---|---|
logo_unreadable | false | The 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_https | false | sourceUrl is not https. Nothing was fetched. Send the same address as https. |
source_not_public | false | sourceUrl itself points at a private or internal address. Nothing was fetched. Use an address anyone on the internet can reach. |
bad_request | false | contentType 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.