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.
Runs the same action as
The REST endpoint and this tool share one action, so validation, permissions and events are identical.
Annotations
A client that honours annotations asks a person before running it. Sending the same arguments twice changes nothing the second time.
Arguments
project_id*stringUUID of the project whose portal design to change.
presetstringoptionalThe 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.
classicdarkmidnightpaperpastelboldcolor_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.
lightdarksystemaccentstringoptionalThe 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 | nulloptionalThe second accent, on the pass-series and pass strips, as a hex colour; `null` paints the preset's own.
backgroundstringoptionalThe page colour behind the cards, as a hex colour.
gridbooleanoptionalWhether the faint dotted grid is drawn over the page colour.
surfacestringoptionalThe cards' colour, as a hex colour.
textstringoptionalThe 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.
headingstringoptionalThe colour of the project's name and every heading, as a hex colour, under the same contrast rule as `text`.
mutedstringoptionalThe colour of the captions, the labels and the small print, as a hex colour, under the same contrast rule as `text`.
taglinestring | nulloptionalOne line under the project's name, up to 80 characters, or `null` for none.
social_linksobject[]optionalUp 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_displaystringoptionalThe 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-displayloramanropesorafont_bodystringoptionalThe face of everything but the headings, from the same twelve as `font_display`.
asap-condensedinterpoppinsmontserratspace-groteskbebas-neueoswalddm-serif-displayplayfair-displayloramanropesoraheading_casestringoptional`uppercase` or `as_written`, for every heading.
uppercaseas_writtentype_scalestringoptional`compact`, `regular` or `large`: one multiplier on every text size.
compactregularlargeradiusstringoptional`sharp`, `soft` or `round` corners on the cards and the fields.
sharpsoftroundbutton_stylestringoptional`pill`, `rounded` or `square` buttons.
pillroundedsquarebutton_fillstringoptional`accent` fills the buttons with the accent, `dark` with the heading colour, `outline` draws them hollow.
accentdarkoutlinebutton_casestringoptional`uppercase` or `as_written`, for every button label.
uppercaseas_writtenshadowstringoptional`none`, `soft` or `strong` shadows under the cards.
nonesoftstrongcard_borderbooleanoptionalWhether 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_rightstackedherocontent_widthstringoptional`narrow`, `regular` or `wide`: how wide the page runs.
narrowregularwidesticky_sidebarbooleanoptionalWhether the sidebar card stays in view while the plans scroll.
plan_gridstringoptional`list`, `two` or `three`: how many plan cards sit side by side.
listtwothreeplan_densitystringoptional`compact` or `roomy` plan cards.
compactroomyplans_headingbooleanoptionalWhether the heading over the plans is shown.
metrics_subscribersbooleanoptionalWhether the member count is shown on the card.
metrics_followersbooleanoptionalWhether the follower count is shown on the card.
metrics_thresholdintegeroptionalThe 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 | nulloptionalThe heading over the plans, up to 60 characters; `null` for the built-in wording.
plans_subtitlestring | nulloptionalThe line under that heading, up to 120 characters; `null` for the built-in wording.
label_loginstring | nulloptionalThe Login button's label, up to 24 characters; `null` for the built-in wording.
label_sharestring | nulloptionalThe Share button's label, up to 24 characters; `null` for the built-in wording.
label_subscribestring | nulloptionalThe button on every plan card, up to 24 characters; `null` for the built-in wording.
checkout_titlestring | nulloptionalThe checkout's heading, up to 60 characters; `null` for the built-in wording.
checkout_subtitlestring | nulloptionalThe line under the checkout's heading, up to 120 characters; `null` for the built-in wording.
checkout_notestring | nulloptionalA note under the checkout, up to 240 characters; `null` for none.
success_titlestring | nulloptionalThe heading of the thank-you screen, up to 60 characters; `null` for the built-in wording.
success_messagestring | nulloptionalThe thank-you screen's message, up to 400 characters; `null` for the built-in wording.
perks_titlestring | nulloptionalThe heading over the perks, up to 60 characters; `null` for the built-in one.
perksstring[]optionalUp to 12 perks, each a line of up to 120 characters; the block shows only with at least one.
faq_titlestring | nulloptionalThe heading over the questions, up to 60 characters; `null` for the built-in one.
faqobject[]optionalUp to 12 questions, each `{question, answer}`, the question up to 160 characters and the answer up to 800, both required.
about_namestring | nulloptionalThe 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 | nulloptionalThe bio on the card about the creator, up to 600 characters, or `null`.
custom_button_labelstring | nulloptionalThe second button's label, up to 24 characters, sent together with `custom_button_url` or both `null`.
custom_button_urlstring | nulloptionalWhere 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 | nulloptionalThe refund policy link in the footer, an https or http address, or `null`.
footer_contact_urlstring | nulloptionalThe contact link in the footer, an https, http or `mailto:` address, or `null`.
footer_imprint_urlstring | nulloptionalThe imprint link in the footer, an https or http address, or `null`.
footer_linestring | nulloptionalA line of small print in the footer, up to 120 characters, or `null`.
footer_helpbooleanoptionalWhether the Help link is shown in the footer.
announcement_textstring | nulloptionalThe strip across the top of the page, up to 160 characters, or `null` for no strip.
announcement_urlstring | nulloptionalWhere the strip leads, an https or http address, or `null`.
announcement_starts_atstring | nulloptionalWhen the strip starts showing, ISO 8601 in any zone and stored in UTC, or `null` for at once.
announcement_ends_atstring | nulloptionalWhen the strip comes down, ISO 8601 and after the start, or `null` for never.
video_urlstring | nulloptionalA YouTube or Vimeo address in any shape the share button gives, stored as the canonical watch address, or `null` for no video.
gallery_titlestring | nulloptionalThe heading over the gallery, up to 60 characters; `null` for the built-in one.
gallerystring[]optionalUp to 8 picture addresses, each stored through `POST /v1/projects/{project}/portal-design/images/gallery`.
testimonials_titlestring | nulloptionalThe heading over the quotes, up to 60 characters; `null` for the built-in one.
testimonialsobject[]optionalUp 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[]optionalThe 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[]optionalThe 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_switcherbooleanoptionalWhether the list of languages under the card is shown.
featured_plan_idstring | nulloptionalThe 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.
alwayslinkshare_buttonbooleanoptionalWhether the Share button is shown in the header.
share_networksstring[]optionalWhich 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_buttonbooleanoptionalWhether the Login button is shown in the header.
social_proofbooleanoptionalWhether a line over the plans says how many members joined in the last seven days; it shows only from three up.
seats_badgesbooleanoptionalWhether a pass series card shows how many seats are left.
seo_indexablebooleanoptionalWhether 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_cardbooleanoptionalWhether a shared link unfurls with a card image; `false` leaves the title and the description alone.
pixel_metastring | nulloptionalThe creator's own Meta Pixel id, 10 to 20 digits, or `null`.
pixel_ga4string | nulloptionalThe creator's own Google Analytics 4 measurement id, `G-` followed by 4 to 15 letters or digits, or `null`.
pixel_tiktokstring | nulloptionalThe creator's own TikTok Pixel id, 10 to 30 letters or digits, or `null`.
pixel_google_adsstring | nulloptionalThe creator's own Google Ads conversion id, `AW-` followed by 6 to 12 digits, or `null`.
custom_cssstring | nulloptionalA 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 | nulloptionalThe words after the project's name in the page title, up to 60 characters, or `null` for the built-in ones.
seo_descriptionstring | nulloptionalThe description search engines and shared links show, up to 160 characters, or `null` for one cut from the project's text.
social_card_urlstring | nulloptionalA 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 | nulloptionalThe language the portal's own address opens in, as a code from `locales`, or `null` for the platform's default.
enfresithibndepttrsilocalesstring[]optionalThe 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_FAILEDa 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_FOUNDproject_id is unknown, or the project belongs to a team outside the token's scope.
AUTHENTICATION_REQUIREDno authenticated user on the request.
TOKEN_MISSING_ABILITYtoken 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