Version
DESTRUCTIVE

update_portal_design

Change some keys of a project's portal design in its draft; anything omitted keeps its drafted value and members keep seeing the published design.

Change the look of a project's portal page in its draft, one key or many: the colours and the preset, the type, the shape, the layout, the wording, the blocks, the button and the footer, the announcement bar, the media, the order and visibility of the parts, the behaviour around the plans, what search engines see, the measurement tags and the stylesheet, and the language. Partial by design: only the arguments present move, and nothing a member sees changes until publish_portal_design. Read the document first with get_portal_design and send the keys back changed; the lists (social_links, perks, faq, gallery, testimonials, sidebar_items, content_blocks, share_networks, locales) are sent whole.

A preset recolours the six colours unless a colour is sent beside it. The design judges every value: a body text, heading or caption colour that blends into the surface or the background (under a 3:1 contrast ratio) is refused, so is a plan the project lacks as featured_plan_id, a list past its ceiling, locales without default_locale, a stylesheet that would load or run, and an announcement that ends before it starts.

Pictures are not arguments. Store one over REST with POST /v1/projects/{project}/portal-design/images/{slot} (or on the dashboard) and write its address into gallery, a quote's avatar_url or social_card_url here.

Requires ability

The token behind the MCP session must hold it, or the call is refused with TOKEN_MISSING_ABILITY.

The REST endpoint and this tool share one action, so validation, permissions and events are identical.

Annotations

DestructiveIdempotent

A client that honours annotations asks a person before running it. Sending the same arguments twice changes nothing the second time.

Arguments

project_id*string

UUID of the project whose portal design to change.

presetstringoptional

The palette the colours started from: `classic`, `dark`, `midnight`, `paper`, `pastel` or `bold`. Sending it recolours the six colours below from the preset and keeps the colour mode and the grid; send a colour beside it to override that one.

classicdarkmidnightpaperpastelbold
color_modestringoptional

`light` or `dark` paints one palette for everyone; `system` paints the light one and switches to the preset's drawn dark palette when the visitor's browser asks for a dark scheme.

lightdarksystem
accentstringoptional

The accent, on the buttons, the highlights and the chosen plan's ring, as a six-digit lowercase hex such as `#a3e635`. The text drawn on it is picked by luminance.

accent_secondarystring | nulloptional

The second accent, on the pass-series and pass strips, as a hex colour; `null` paints the preset's own.

backgroundstringoptional

The page colour behind the cards, as a hex colour.

gridbooleanoptional

Whether the faint dotted grid is drawn over the page colour.

surfacestringoptional

The cards' colour, as a hex colour.

textstringoptional

The body text colour, as a hex colour. It must read on the surface and on the background: a pair under a 3:1 contrast ratio is refused.

headingstringoptional

The colour of the project's name and every heading, as a hex colour, under the same contrast rule as `text`.

mutedstringoptional

The colour of the captions, the labels and the small print, as a hex colour, under the same contrast rule as `text`.

taglinestring | nulloptional

One line under the project's name, up to 80 characters, or `null` for none.

social_linksobject[]optional

Up to 12 links under the card, each `{network, url, label}`: `network` one of `website`, `instagram`, `x`, `youtube`, `tiktok`, `facebook`, `linkedin`, `discord`, `telegram`, `whatsapp`, `threads`, `custom`; `url` an https address; `label` the name shown for a `custom` link, up to 40 characters, ignored on the others.

font_displaystringoptional

The face of the headings: `asap-condensed`, `inter`, `poppins`, `montserrat`, `space-grotesk`, `bebas-neue`, `oswald`, `dm-serif-display`, `playfair-display`, `lora`, `manrope` or `sora`.

asap-condensedinterpoppinsmontserratspace-groteskbebas-neueoswalddm-serif-displayplayfair-displayloramanropesora
font_bodystringoptional

The face of everything but the headings, from the same twelve as `font_display`.

asap-condensedinterpoppinsmontserratspace-groteskbebas-neueoswalddm-serif-displayplayfair-displayloramanropesora
heading_casestringoptional

`uppercase` or `as_written`, for every heading.

uppercaseas_written
type_scalestringoptional

`compact`, `regular` or `large`: one multiplier on every text size.

compactregularlarge
radiusstringoptional

`sharp`, `soft` or `round` corners on the cards and the fields.

sharpsoftround
button_stylestringoptional

`pill`, `rounded` or `square` buttons.

pillroundedsquare
button_fillstringoptional

`accent` fills the buttons with the accent, `dark` with the heading colour, `outline` draws them hollow.

accentdarkoutline
button_casestringoptional

`uppercase` or `as_written`, for every button label.

uppercaseas_written
shadowstringoptional

`none`, `soft` or `strong` shadows under the cards.

nonesoftstrong
card_borderbooleanoptional

Whether the cards carry a hairline border.

arrangementstringoptional

`sidebar_left` or `sidebar_right` put the card beside the plans, `stacked` puts it above them, `hero` runs the banner across the top.

sidebar_leftsidebar_rightstackedhero
content_widthstringoptional

`narrow`, `regular` or `wide`: how wide the page runs.

narrowregularwide
sticky_sidebarbooleanoptional

Whether the sidebar card stays in view while the plans scroll.

plan_gridstringoptional

`list`, `two` or `three`: how many plan cards sit side by side.

listtwothree
plan_densitystringoptional

`compact` or `roomy` plan cards.

compactroomy
plans_headingbooleanoptional

Whether the heading over the plans is shown.

metrics_subscribersbooleanoptional

Whether the member count is shown on the card.

metrics_followersbooleanoptional

Whether the follower count is shown on the card.

metrics_thresholdintegeroptional

The count a figure must reach before it is shown, 0 to 1,000,000, so a young project never shows a small number.

plans_titlestring | nulloptional

The heading over the plans, up to 60 characters; `null` for the built-in wording.

plans_subtitlestring | nulloptional

The line under that heading, up to 120 characters; `null` for the built-in wording.

label_loginstring | nulloptional

The Login button's label, up to 24 characters; `null` for the built-in wording.

label_sharestring | nulloptional

The Share button's label, up to 24 characters; `null` for the built-in wording.

label_subscribestring | nulloptional

The button on every plan card, up to 24 characters; `null` for the built-in wording.

checkout_titlestring | nulloptional

The checkout's heading, up to 60 characters; `null` for the built-in wording.

checkout_subtitlestring | nulloptional

The line under the checkout's heading, up to 120 characters; `null` for the built-in wording.

checkout_notestring | nulloptional

A note under the checkout, up to 240 characters; `null` for none.

success_titlestring | nulloptional

The heading of the thank-you screen, up to 60 characters; `null` for the built-in wording.

success_messagestring | nulloptional

The thank-you screen's message, up to 400 characters; `null` for the built-in wording.

perks_titlestring | nulloptional

The heading over the perks, up to 60 characters; `null` for the built-in one.

perksstring[]optional

Up to 12 perks, each a line of up to 120 characters; the block shows only with at least one.

faq_titlestring | nulloptional

The heading over the questions, up to 60 characters; `null` for the built-in one.

faqobject[]optional

Up to 12 questions, each `{question, answer}`, the question up to 160 characters and the answer up to 800, both required.

about_namestring | nulloptional

The name on the card about the creator, up to 60 characters, or `null`; the card shows when a name or a bio is set.

about_biostring | nulloptional

The bio on the card about the creator, up to 600 characters, or `null`.

custom_button_labelstring | nulloptional

The second button's label, up to 24 characters, sent together with `custom_button_url` or both `null`.

custom_button_urlstring | nulloptional

Where the second button leads, an https or http address up to 255 characters, sent together with `custom_button_label` or both `null`.

footer_refund_urlstring | nulloptional

The refund policy link in the footer, an https or http address, or `null`.

footer_contact_urlstring | nulloptional

The contact link in the footer, an https, http or `mailto:` address, or `null`.

footer_imprint_urlstring | nulloptional

The imprint link in the footer, an https or http address, or `null`.

footer_linestring | nulloptional

A line of small print in the footer, up to 120 characters, or `null`.

footer_helpbooleanoptional

Whether the Help link is shown in the footer.

announcement_textstring | nulloptional

The strip across the top of the page, up to 160 characters, or `null` for no strip.

announcement_urlstring | nulloptional

Where the strip leads, an https or http address, or `null`.

announcement_starts_atstring | nulloptional

When the strip starts showing, ISO 8601 in any zone and stored in UTC, or `null` for at once.

announcement_ends_atstring | nulloptional

When the strip comes down, ISO 8601 and after the start, or `null` for never.

video_urlstring | nulloptional

A YouTube or Vimeo address in any shape the share button gives, stored as the canonical watch address, or `null` for no video.

gallery_titlestring | nulloptional

The heading over the gallery, up to 60 characters; `null` for the built-in one.

gallerystring[]optional

Up to 8 picture addresses, each stored through `POST /v1/projects/{project}/portal-design/images/gallery`.

testimonials_titlestring | nulloptional

The heading over the quotes, up to 60 characters; `null` for the built-in one.

testimonialsobject[]optional

Up to 12 quotes from members, each `{quote, name, subtitle, avatar_url}`: the words up to 300 characters and the name up to 60, both required; `subtitle` what they are, up to 60 characters, or `null`; `avatar_url` a photo stored through `POST /v1/projects/{project}/portal-design/images/avatar`, or `null` for the first letter of the name.

sidebar_itemsobject[]optional

The sidebar card's parts in order, each `{key, shown}` with `key` one of `tagline`, `metrics`, `buttons`, `description`, `social_links`, `footer`, every key exactly once; a part left out is appended, shown.

content_blocksobject[]optional

The blocks under the plans in order, each `{key, shown}` with `key` one of `perks`, `about`, `faq`, `video`, `gallery`, `testimonials`, every key exactly once; a block left out is appended, shown.

locale_switcherbooleanoptional

Whether the list of languages under the card is shown.

featured_plan_idstring | nulloptional

The plan that carries the Most popular badge, one of the project's own plan ids, or `null` for none.

coupon_boxstringoptional

`always` shows the coupon box on every checkout; `link` shows it only once a coupon arrived with the visit.

alwayslink
share_buttonbooleanoptional

Whether the Share button is shown in the header.

share_networksstring[]optional

Which networks the share sheet offers, kept in the sheet's own order: `bluesky`, `buffer`, `email`, `facebook`, `flipboard`, `line`, `linkedin`, `mix`, `pinterest`, `reddit`, `telegram`, `threads`, `tumblr`, `x`, `vk`, `whatsapp`, `xing`.

login_buttonbooleanoptional

Whether the Login button is shown in the header.

social_proofbooleanoptional

Whether a line over the plans says how many members joined in the last seven days; it shows only from three up.

seats_badgesbooleanoptional

Whether a pass series card shows how many seats are left.

seo_indexablebooleanoptional

Whether search engines may list the portal; `false` adds a noindex directive. A portal the project's own readiness keeps out stays unlisted either way.

seo_social_cardbooleanoptional

Whether a shared link unfurls with a card image; `false` leaves the title and the description alone.

pixel_metastring | nulloptional

The creator's own Meta Pixel id, 10 to 20 digits, or `null`.

pixel_ga4string | nulloptional

The creator's own Google Analytics 4 measurement id, `G-` followed by 4 to 15 letters or digits, or `null`.

pixel_tiktokstring | nulloptional

The creator's own TikTok Pixel id, 10 to 30 letters or digits, or `null`.

pixel_google_adsstring | nulloptional

The creator's own Google Ads conversion id, `AW-` followed by 6 to 12 digits, or `null`.

custom_cssstring | nulloptional

A stylesheet of up to 16,384 characters printed after the theme so its rules win; refused when it would load, run or escape (`@import`, `url(`, `expression(`, `javascript:`, a closing `style` tag and the like); `null` for none.

seo_title_suffixstring | nulloptional

The words after the project's name in the page title, up to 60 characters, or `null` for the built-in ones.

seo_descriptionstring | nulloptional

The description search engines and shared links show, up to 160 characters, or `null` for one cut from the project's text.

social_card_urlstring | nulloptional

A card image of the creator's own, stored through `POST /v1/projects/{project}/portal-design/images/card` and sent in the generated card's place, or `null` for the generated card.

default_localestring | nulloptional

The language the portal's own address opens in, as a code from `locales`, or `null` for the platform's default.

enfresithibndepttrsi
localesstring[]optional

The languages the switcher offers, as codes from `en`, `fr`, `es`, `it`, `hi`, `bn`, `de`, `pt`, `tr`, `si`, never empty and always carrying `default_locale`.

What it returns

{  "data": {    "project_id": "7f3d1c92-8b45-4e6a-9d21-5c8e0a4b6f13",    "published": {      "preset": "classic",      "accent": "#f43f5e",      "font_display": "asap-condensed"    },    "draft": {      "preset": "midnight",      "accent": "#22d3ee",      "font_display": "space-grotesk"    },    "has_draft": true,    "draft_updated_at": "2026-10-26T16:12:45+00:00",    "published_at": "2026-10-20T09:30:00+00:00",    "preview_url": "https://my.subscriby.net/research-premium?preview=7f3d1c92-8b45-4e6a-9d21-5c8e0a4b6f13&expires=1793030400&signature=4b8f…"  }}

The whole design after the change, the same object get_portal_design returns, both documents carrying every key; the example shows a few of them.

How it fails

VALIDATION_FAILED

a value has the wrong type or is not one the portal can draw: a colour that is not hex, a text colour that blends into its ground, a plan the project lacks, a list past its ceiling, a language list without its default, a stylesheet that loads or runs. The envelope names the key.

RESOURCE_NOT_FOUND

project_id is unknown, or the project belongs to a team outside the token's scope.

AUTHENTICATION_REQUIRED

no authenticated user on the request.

TOKEN_MISSING_ABILITY

token lacks project:update, or its owner's role on the project's team may not change the project.

How is this guide?

Last updated on