Portal design

Make the partner portal on your own domain look and read like your product: colours with a dark scheme, fonts, logos, the header and footer, the home page, navigation, the sign-in page, wording, custom CSS, and embedding it in your app.

Embed
Preview of the card
<a href="https://referalli.com/docs/portal-design"><img src="https://referalli.com/docs/_api/public/orgs/07f432f4-34b9-46a7-aaad-c0e395d2e4f9/spaces/2bf4f614-3630-4fbf-85f9-5ecafa6b7d97/pages/54fd6cb1-3d0b-44c8-b7e3-04296bf0a989/social-card.svg?v=3e7ae33eec8b" alt="Portal design" width="600" height="315"></a>

Your partners use the portal on your own domain (partners.yourapp.com, see Hosted pages). There it runs your consent banner, your support chat and your sign-in. Portal design decides how it looks and reads.

Design it in the dashboard under Portal design, or over the API. Every setting is optional: anything you leave unset keeps the standard look.

Draft, preview, publish

You always edit a draft. Partners keep seeing the published design until you publish.

Call

What it does

GET /v1/portal/theme

published, draft and when each changed

PATCH /v1/portal/theme/draft

Merges into the draft: objects key by key, lists and values replace, null removes a key

PUT /v1/portal/theme/draft

Replaces the whole draft

POST /v1/portal/theme/preview

A link that shows the draft on your domain for 7 days, to anyone who has it

POST /v1/portal/theme/publish

Makes the draft live (within a minute) and keeps it as a version

GET /v1/portal/theme/versions, POST …/versions/{id}/restore

Every published version, and a way back

GET /v1/portal/theme/schema

Every key, as JSON Schema

POST /v1/portal/theme/import-brand

A design-tokens brand.json merged into the draft

An unknown key is refused with a 422 that names its full path (chrome.footer.colums), so a write never quietly changes nothing.

curl -X PATCH https://referalli.com/v1/portal/theme/draft \
  -H "Authorization: Bearer $REFERALLI_API_KEY" -H "Content-Type: application/json" \
  -d '{
    "primaryColor": "#4f46e5",
    "paletteDark": { "background": "#0b1020", "primary": "#818cf8" },
    "bodyFontFamily": "Inter",
    "customFontUrl": "https://fonts.googleapis.com/css2?family=Inter:wght@400;600",
    "portal": { "navigation": { "hidden": ["financing", "marketplace"] } },
    "chrome": { "footer": { "showPoweredBy": false } }
  }'

What you can set

Area

Keys

Brand

brandName (or false), logoUrl, logoUrlLight, logoUrlDark, logoMark, logoIncludesName, logoDarkMode (invert, plate), logoHeight, faviconUrl, pageTitle, mobile.logoUrl

Colours

primaryColor, primaryColorDark, accentColor. palette and paletteDark have these slots: background, surface, surfaceAlt, ink, muted, faint, line, lineStrong, primary, onPrimary, primarySoft, header_background, header_primary, sidebar, footer_background, footer_primary, success, warning, danger, info, chart, money. Also colorScheme (system, light, dark) and colorSchemeStrict

Type

font, customFontUrl (a stylesheet), fonts.faces (your own @font-face files), headingFontFamily, bodyFontFamily, moneyFontFamily, monoFontFamily. typography has baseSize, headingScale, headingWeight, bodyWeight, headingTransform, headingTracking, bodyLeading and numerals

Layout

contentWidth (standard, wide, full), density. portal.navigation has style (topbar, sidebar), primary, hidden and labels. portal.sections sets the order of home's sections and can hide or rename each one: welcome, earnings, link, stats, groups, milestones, programs. portal.welcome is your own block at the top of home

Shape

components has radius, radiusSmall, shadow, cards, buttons (radius, case, weight), inputs, focusRing and links (underline, colour)

Header and footer

chrome.header has links, cta, sticky, border, brandAlign, showLocaleSwitcher and showThemeToggle. chrome.footer has columns, text, social and showPoweredBy. announcement has text, level, link, dismissible, startsAt and endsAt

Sign-in page

portal.signIn has title, subtitle, layout (split puts your image beside the form) and footnote

Wording

stringOverrides: { "portal.navEarnings": { "en": "Commissions" } }. Other languages keep the standard text

Phones

mobile has fontScale, density, logoUrl, hideSections and css

Escape hatches

customCss, cssSnippets (named and switchable), and settingsSchema / settings (typed values your CSS reads as --setting-<key>)

Custom CSS is sanitised: no @import, nothing that could leave the stylesheet or run script. It only reaches your portal domain. The settings above are meant to make it unnecessary, so prefer them.

Fonts and images may come from any https host. The portal's Content-Security-Policy adds your font hosts by itself, including both Google Fonts hosts.

Embed the portal in your app

Turn on embed to show the portal inside your own app in an iframe:

{ "embed": { "enabled": true, "allowedOrigins": ["https://app.yourapp.com"], "hideChrome": true } }

Only the origins you list may frame the portal (frame-ancestors). hideChrome drops the portal's own header and footer inside the frame, so it reads as part of your app. Sign partners in with your own session through the JWT method in Partner sign-in with your identity.

The portal's sign-in cookie is SameSite=Lax. Keep the portal domain on the same site as your app (partners.yourapp.com inside app.yourapp.com). Browsers don't send the cookie to a frame from another site.

Brand files

POST /v1/portal/theme/import-brand takes a W3C design-tokens file, either as document or as an https url. It reads color.primary, color.background, color.surface, color.text, color.muted, color.border, the status colours, font.body, font.heading, font.mono, font.url, radius.base, logo.url and brand.name. The answer lists the tokens it applied, in applied.

Updated