Folloze API

Folloze API

The Folloze API lets you build personalized buyer experiences programmatically — generate boards from templates, sync your content library, and attach the right content to the right account — all driven from your CRM, CMS, or AI pipeline.

The platform exposes two complementary REST APIs, both served from the same host (https://content-api.folloze.com). Most integrations use both:

APIWhat it does
Board Creation API Generates Folloze boards from template boards — the structure, sections, widgets, headers, and footers of the page itself.
Content API Creates content items in Folloze from any source (URLs, files, videos, PDFs — the asset doesn't have to exist in Folloze first), and places items on boards with per-board overrides.

Both APIs share the same base URL, authentication, error model, and conventions described in the next few sections. A single API token issued to your organization unlocks both.

Typical use cases

Which API do I need? #

Pick by what you're trying to ship to a buyer:

End-to-end example

A typical "generate a personalized board for an account" flow uses both APIs:

  1. GET /v1/board_templates — list templates in your organization.
  2. GET /v1/board_templates/:id/describe — read a template's full structure (sections, widgets, header, footer).
  3. POST /v1/boards — create a new board from a template.
  4. POST /v1/items — create a content item.
  5. GET /v1/items — list and search content items.
  6. POST /v1/board_items — place a content item on a board.

Board Creation API: overview #

The Board Creation API turns a template board in your organization into the source-of-truth for many personalized boards. You read the template's structure, fill in your content, and post it back to create a new board.

Core endpoints

EndpointPurpose
GET /v1/board_templatesList all template boards in your organization — useful for discovering template ids, names, and descriptions.
GET /v1/board_templates/:id/describeRead a full template structure, including header and footer, in the same shape used for creation.
POST /v1/boardsCreate a new board from a template, with section content (and optionally header/footer).
PATCH /v1/boards/:board_idUpdate an existing board's sections, widgets, slug, and settings.

Prerequisites #

  1. Feature enabled. The board_creation_api feature must be enabled for your organization. Contact your Folloze account manager if it is not.
  2. API token. An API token issued by Folloze (a “Content Integration Client” token). To get a token, contact your Folloze account representative — tokens are provisioned per organization and cannot be self-served.
  3. A template board. A board that is defined as a template in your Folloze instance.

Authentication #

All requests are authenticated with your API token in the Authorization header, using the Basic scheme:

Authorization: Basic <your_api_token>

Requests without a valid token return 401. Requests where the API is not enabled in your Folloze organization return 403.

Getting a token. API tokens are not self-served. Contact your Folloze account representative to have a token provisioned for your organization. The same token works for both the Board Creation API and the Content API.

Required headers #

These header rules apply to both the Board Creation API and the Content API.

All POST / PUT requests

Every write call must include these headers in addition to Authorization:

HeaderValueWhy
Content-Typeapplication/jsonRequired — bodies are parsed as JSON.
User-AgentFollozeRequired — identifies the request as an official Folloze API client. Requests without it may be rejected.
Folloze-InitiatorFollozeRequired — identifies the integration source so Folloze can route the call through the appropriate ingress path.

Example

POST https://content-api.folloze.com/v1/items
Authorization:     Basic <your_api_token>
Content-Type:      application/json
User-Agent:        Folloze
Folloze-Initiator: Folloze

{ ... }
Don't omit User-Agent. Many HTTP clients leave User-Agent off by default. Set it explicitly to Folloze on every write call.

Base URL #

Both the Board Creation API and the Content API are served from the same host:

https://content-api.folloze.com

All endpoint paths in this documentation are relative to this base URL.

Response codes #

StatusMeaning
200Success — payload returned in the response body.
400Malformed request (e.g. sections missing or not an array).
401Authentication missing or invalid.
403The API is not enabled in your Folloze organization.
404Template board (or referenced footer_id) not found in the organization.
422The request was understood but rejected by validation — an unknown field, a bad enum value, a wrong type, a missing section id, or an image Folloze could not fetch. The body carries an errors array naming each offending field path.
500Unexpected server error. Not a validation signal — if a create or update returns 500, report it.

Concepts: sections, widgets, ribbons #

A Folloze board is built from sections (e.g. a banner, a content block, a form). Each section contains one or more widgets (the actual content — a banner widget with title / subtitle / cta) and optionally ribbon parts (background and decorative layers).

When you call the API, you describe each section as a JSON object that mirrors the API schema of the widgets that section uses.

Ribbons (section backgrounds) #

A section's background is exposed as a ribbon object, a sibling of widgets. Sections that have no background omit the key entirely.

A ribbon can be updated on its own. Send ribbon without widgets to change only a section’s background.
"ribbon": {
  "background": { "type": "color", "value": "background: #ffffff" },
  "backgroundMobile": { "type": "color", "value": "background: var(--fz-color-neutral-1000)" },
  "optimizedForMobile": false
}
FieldTypeDescription
backgroundobjectThe desktop background. Discriminated by type — see below.
backgroundMobileobjectOptional mobile-specific background, same shape. Send null to remove it.
optimizedForMobilebooleanWhether the mobile background is used.

Background kinds

typeFieldsWritableNotes
color value (string) yes value is a full CSS declaration, not a bare color — "background: #ffffff", not "#ffffff". Theme variables work: "background: var(--fz-color-primary-3)". Gradients are expressed the same way.
image url (string), image_config (object) yes image_config accepts shape, crop, flipX, flipY, filter, and tint — no fit on ribbons. cover is read-only. See Configuring images.
video url (string) no Returned by /describe for sections with a video background, but not accepted on write. Leave such a ribbon out of your payload rather than trying to round-trip it.

Example: image background

"ribbon": {
  "background": {
    "type": "image",
    "url": "https://images.folloze.com/image/upload/hpifscdihf1mw21yytsy.jpg",
    "image_config": {
      "shape": "none",
      "flipX": false,
      "flipY": false,
      "filter": "none",
      "tint": { "color": "#081d4d", "alpha": 78 },
      "crop": { "x": 1, "y": 154, "width": 1723, "height": 672 }
    },
    "cover": { "width": 1920 }
  },
  "optimizedForMobile": false
}
Switching kinds is allowed. Changing a ribbon from color to image (or back) works — send the new background object and the layout fields are rebuilt. When you keep the same kind, unspecified fields are preserved from the existing ribbon.

API schema descriptors #

Each widget exposes a small, stable contract — only the fields meant for external content are exposed. For example, the Banner widget exposes:

{
  "title": "string",
  "title_size": "wide | narrow",
  "subtitle": "string",
  "subtitle_size": "wide | narrow",
  "cta": { "text": "string", "type": "string", "action": { } },
  "secondary_cta": { "text": "string", "type": "string", "action": { } },
  "promoted_form": { "type": "string" },
  "content_alignment": "string",
  "mobile_alignment": "string",
  "min_height": "short | medium | tall",
  "visibility": {
    "title": "boolean", "subtitle": "boolean", "showButtons": "boolean",
    "cta": "boolean", "secondary_cta": "boolean", "promoted_form": "boolean"
  },
  "animate": "boolean"
}

The Promo — Columns widget exposes a repeatable list of columns:

{
  "title": "string",
  "subtitle": "string",
  "cta": { "text": "string" },
  "repeatable": {
    "columns": [
      {
        "title": "string",
        "subtitle": "string",
        "paragraph": "string",
        "cta": { "text": "string" },
        "visibility": "boolean"
      }
    ]
  }
}

Other widgets follow the same pattern (Single Item, Image Section, Side-by-side, Q&A, Multi-tab, Carousel, etc.). Use GET /v1/board_templates/:id/describe to retrieve the exact descriptor for any template.

Widget catalog #

The keys inside a section's widgets object are widget keys. These are the only keys the API recognises — anything else is rejected. Several widgets are versioned variants that share one schema, so promo-columns-2 takes exactly the same fields as promo-columns.

Body & content widgets

Widget keySchemaWhat it is
bannerownHero banner — title, subtitle, two CTAs, promoted form.
side-by-sideownTwo-column block; each side holds an image, a video, or a paragraph.
split-banner= side-by-sideBanner-styled split layout.
image-sectionownFull-width image with title, subtitle, and CTAs.
text-sectionownTitle, subtitle, and CTAs only — no media.
promoownSingle promotional block.
promo-2= promoVariant of the above.
promo-columnsownRepeatable columns of promo cards.
promo-columns-2= promo-columnsVariant — commonly used for stat rows.
running-carouselownContinuously scrolling column strip.
multi-tabownTabbed side-by-side content.
multi-tab-columnsownTabbed column content.
qa-sectionownQuestion / answer accordion rows.
single-itemownOne featured content item.
single-item-2= single-itemVariant of the above.
video-sectionownEmbedded video item.
content-carouselownCarousel of content items.
content-promotedownPromoted content items.
content-tabownContent items grouped into tabs.
content-tab-2= content-tabVariant of the above.
content-spread-2ownContent grid / spread. Note the -2 — see the warning below.
form-sectionownInline form section.
zoomownZoom live event. See Event sections.
simulive-eventownSimulive event. See Event sections.
html-sectionownCustom HTML block. See below.

Structural widgets

Widget keyWhat it is
headerBoard header. Set via the page's header object, not in a body section.
html-headerCustom-HTML header.
html-footerCustom-HTML footer.

Floating widgets

These live under the top-level floating_sections array, not inside a page's sections.

Widget keyWhat it is
back-to-topBack-to-top control.
contact-cardFloating contact card.
floating-formFloating form.
Floating sections are keyed by floating_id, not section_id. Each entry carries floating_id, type, and widgets — there is no page prefix on the id, and no ribbon. floating_sections sits at the top level of the describe response, beside theme_id and main_page, because floating widgets are board-wide rather than per page.
"floating_sections": [
  { "type": "back-to-top", "floating_id": "f_c94a84c4",
    "widgets": { "back-to-top": { "shape": "square", "icon_color": "var(--fz-color-neutral-0)", "background_color": "var(--fz-color-primary-3)" } } },
  { "type": "contact-card",  "floating_id": "f_f2cf719d", "widgets": {} },
  { "type": "floating-form", "floating_id": "f_5163d98b", "widgets": {} }
]
A section can come back with "widgets": {}. Some sections have no editable API schema. They describe with their section_id, type, name, and ribbon, but an empty widgets object. The section still exists and survives a round-trip untouched — its content simply cannot be edited through the API. Edit those in Designer, or add items with POST /v1/board_items after creation.

Simple widget schemas

A few widgets are small enough to state in full here. For every other widget, read the descriptor from /describe.

text-section:

FieldTypeDescription
titlestringRich-text heading.
subtitlestringRich-text body copy.
ctaobjectCTA object.
secondary_ctaobjectCTA object.

image-section — same as text-section plus:

FieldTypeDescription
imageobjectEditable image (url, alt, link, image_config). See Configuring images.

side-by-side (and split-banner): title (string), subtitle (string), left (side), right (side).

Each side is discriminated by type, and only the block named by type is read — send type together with the block it names:

typeBlockFields
imageimageimage (editable image), image_size: "small" | "medium" | "large"
paragraphparagraphtitle, subtitle, cta, secondary_cta, color {title, subtitle}, visibility {title, subtitle}
videovideopreview_image.image (editable image), flz_item_id (item id)
form—Nothing to set here; a form side is configured in Designer.
"side-by-side": {
  "title": "<h2>Why teams switch</h2>",
  "left":  { "type": "paragraph", "paragraph": { "title": "<div>Best of them all</div>", "visibility": { "title": true, "subtitle": true } } },
  "right": { "type": "image", "image": { "image": { "url": "https://cdn.example.com/photo.png", "alt": "Customer" }, "image_size": "medium" } }
}

html-section:

FieldTypeDescription
contenthtmlThe raw HTML. Never returned by /describe — see the note below.
scriptLocationstring"head" or "body" — where any scripts in the content are injected.
animatebooleanWhether the section animates into view.
html-typed fields are stripped from /describe. This is deliberate, not a bug — raw HTML would bloat the response. An html-section therefore describes as just { "scriptLocation": "head", "animate": true } with no content key, and a round-trip will not carry the HTML over. Read and write HTML through the dedicated HTML section endpoints instead, and remember that HTML content is not cloned when you create a board from a template — PUT it onto the new board afterwards.

Reading the authoritative schema #

The widget descriptors are served data, not a frozen contract in this document. New widgets ship, fields are added, and enums gain values between releases of this page. Anything here that disagrees with what the API returns today, the API wins.

Build integrations so they read the shape at runtime rather than hard-coding a snapshot:

StepCallWhy
1GET /v1/board_templates/:id/describe (or /v1/boards/:id/describe)Current sections, ids, and values — the shape you must post back.
2Inspect the section you intend to writeField names and accepted values for exactly that section’s widgets.
3POST /v1/boards / PATCH /v1/boards/:idWrite only the sections and fields you are changing.
Describe before every write, not once per integration. Section ids, widget keys, and enum values are all resolved against the board’s current configuration. A payload built from a snapshot taken weeks earlier can fail against a board that has since been edited in Designer — most often with Section id "..." not found. Fetching describe immediately before a create or update is the supported pattern, and costs one call.

Validation is strict and positive

Writes are validated against the descriptor for each section:

All of these come back as 422 with an errors array. A 500 from these endpoints is a defect, not a validation signal — report it.

Validation is per widget, not per field

Writes are partial — you send only the fields you are changing — but validation is not scoped that narrowly. Editing any field of a widget re-validates that whole widget, including fields you did not send.

This matters most where fields constrain each other. Changing an image’s shape from rectangle to square re-checks the stored crop against the new shape’s 1:1 ratio, even though you only sent shape:

// sent
"image_config": { "shape": "square" }

// rejected, because the crop already stored is 16:9
Section 'board:default:s_4a5e83a5' [0].widgets.image-section.image.image_config.crop:
  crop does not match the 1:1 ratio required by shape "square"

So when you change something that constrains another field, send both together — a new shape with the crop that fits it. Narrowing the payload does not narrow the validation.

Describe output is meant to be publishable as-is. Round-tripping /describe through create or update is a contract Folloze upholds: what you read back should be accepted unchanged. If a verbatim clone is rejected, that is a bug worth reporting rather than something to design around — send us the 422 and the board id.

One live exception is tracked today: some boards and templates carry a stored image_size of "medium_large" or "small_medium" that the validator rejects. It is being fixed; in the meantime map medium_large to large and small_medium to medium.


Section & widget identity #

The API matches sections by section_id — the id you receive from GET /v1/board_templates/:id/describe (stable across calls for the same template).

section_id is an opaque string. Its internal format is not part of the contract and has changed before, so treat it as a token to carry, never to parse or construct:

"section_id": "board:default:s_f316586e"
Round-trip the id verbatim. Send back exactly the section_id string /describe gave you, under the same page object you read it from. A rebuilt, stripped, or hand-written id fails with Section id "…" not found. The format changed on 2026-08-20, so ids captured before then no longer resolve — re-read /describe to refresh any you have stored.

The same applies to widgets within a section: provide widget_id to update specific widgets; otherwise widgets are matched by their order.

Two things to watch.
  • If you don't include a section_id for a section, that section will not be added to the new board.
  • The order of sections in your sections array is meaningful — the new board's sections will appear in the same order you send them.

Merge semantics #


Configuring CTAs #

Call-to-action buttons can be configured on any section that exposes a cta or secondary_cta field — headers, banners, body sections, navigation items, and carousel columns. Every CTA follows the same object shape regardless of where it appears.

CTA object

FieldTypeDescription
textstringrequiredThe button label displayed to visitors.
typestringoptionalButton style. One of the nine values in Button styles. Navigation items default to "flz-link".
prefixstringoptionalIcon or glyph rendered before the label.
suffixstringoptionalIcon or glyph rendered after the label.
actionobjectoptionalWhat happens when the visitor clicks. Contains a type field plus a config key of the same name. See action types below.

Promoted form (promoted_form)

Some widgets — the banner most notably — expose a promoted_form field alongside cta and secondary_cta. It renders a form inline in the section instead of behind a button click.

promoted_form is a bare action, not a button. It takes the action object directly — { "type": …, "<type>": { … } } — with no text, type, prefix, or suffix wrapper. Sending it shaped like a CTA is rejected.
"promoted_form": {
  "type": "form",
  "form": {
    "form_id": 173866,
    "privacy_message_id": 3000,
    "form_title": "",
    "send_mail": {
      "email_template_id": 0,
      "is_enabled": false,
      "sender_type": "inviter",
      "sender": { "id": 0, "email": "" }
    }
  }
}

In practice you will use form or registration here (see form config), but any action type is accepted. Like the buttons, it is gated by visibility — set visibility.promoted_form: true or it will not render.

Button styles

The type field accepts exactly these nine values. Anything else is rejected. The concrete look of each comes from the board theme, so the same value renders differently across brands.

ValueAppearance
flz-primarySolid, high-emphasis button — the main call to action.
flz-secondaryOutlined or lower-emphasis button — a supporting action.
flz-linkStyled as an inline link. Default for header navigation items.
flz-link-2Alternate link style defined by the theme.
flz-custom-1Theme-defined custom style slot 1.
flz-custom-2Theme-defined custom style slot 2.
flz-curvedPill / fully-rounded button.
flz-circleCircular icon button — pair with prefix / suffix.
flz-lightLight-on-dark variant, for use over dark ribbons and imagery.

CTA visibility

Setting cta, secondary_cta, or promoted_form only defines what the button is — it does not make it appear. Whether a button renders is controlled by the widget's visibility map, which /describe returns alongside the content fields:

{
  "widgets": {
    "banner": {
      "cta":           { "text": "Book a demo",    "type": "flz-primary",   "action": { ... } },
      "secondary_cta": { "text": "Watch the tour", "type": "flz-secondary", "action": { ... } },
      "visibility": {
        "showButtons": true,
        "cta": true,
        "secondary_cta": true,
        "promoted_form": false
      }
    }
  }
}
FlagControls
showButtonsMaster switch for the button row. When false, no CTA renders no matter what the individual flags say. Present on widgets that group their buttons (e.g. Banner).
ctaThe primary CTA.
secondary_ctaThe secondary CTA.
promoted_formThe inline promoted form, on widgets that support one.
The flag set is per widget. A header exposes cta, navigation, share_button, primary_logo, tag_line…; a banner exposes title, subtitle, showButtons, cta, secondary_cta, promoted_form; other sections expose their own. Read the visibility object your template actually returns and round-trip the whole map rather than assuming a fixed shape.

Action types

The action object always has a type field that selects the behaviour, plus a sibling key of exactly the same name containing the type-specific configuration. There are no exceptions — { "type": "form", "form": { … } }, { "type": "open_content_item", "open_content_item": { … } }, and so on.

action.typeConfig keyFieldsDescription
open_url open_url url (string), open_in_new_window (bool) Navigate to an external or internal URL. Set open_in_new_window: true to open in a new tab.
form form See form config Open a Folloze form. form_id is a number. The config carries far more than the id — title, success behaviour, redirect, layout, and follow-up mail.
registration registration See form config Identical shape to form, but treated as event registration.
anchor anchor hash (string) Scroll to a named anchor on the same board page.
contact contact privacy_message_id (number) Open a “contact me” dialog tied to a privacy message.
message message privacy_message_id (number) Open a “send a message” dialog tied to a privacy message.
share share privacy_message_id (number), send_mail (object) Open the board share dialog tied to a privacy message. send_mail has the same shape as in the form config.
send_email_clicked send_email_clicked email (string), subject (string) Open the visitor's email client with a pre-filled recipient and subject line.
open_content_item open_content_item flz_item_id (integer | object), design (string), allow_download (boolean) Open a content item in the viewer overlay. flz_item_id accepts a plain integer or { "content_center_item_id": <id> }. See detailed guide.

Form config (form / registration)

The form and registration actions take a full configuration object, not just an id. /describe returns the whole thing — round-trip what you don't intend to change.

"action": {
  "type": "form",
  "form": {
    "form_id": 82839,
    "privacy_message_id": 0,
    "form_title": "See how this applies to your organization",
    "success_message": "",
    "submit_redirect_url": "",
    "submit_redirect_new_tab": false,
    "columns_layout": "single",
    "send_mail": {
      "email_template_id": 0,
      "is_enabled": false,
      "sender_type": "inviter",
      "sender": { "id": 0, "email": "" }
    },
    "success_message_style": {
      "duration": 5,
      "text_color": "#ffffff",
      "background": "#000000",
      "top_line_color": "#00b0ff"
    }
  }
}
FieldTypeDescription
form_idnumberId of the Folloze form to open. A number, not a string — a numeric string is rejected with expected number, got string.
privacy_message_idnumberPrivacy / consent message shown with the form. 0 means none.
form_titlestringHeading shown above the form fields.
success_messagestringMessage shown after a successful submit.
submit_redirect_urlstringOptional URL to send the visitor to after submitting.
submit_redirect_new_tabbooleanOpen submit_redirect_url in a new tab.
columns_layoutstringField layout. "single" or "double".
send_mailobjectFollow-up email on submit — see below.
success_message_styleobjectduration (number, seconds), text_color, background, top_line_color (strings).

send_mail object (also used by the share action):

FieldTypeDescription
is_enabledbooleanWhether the follow-up email is sent at all.
email_template_idnumberEmail template to send. 0 means none.
sender_typestring"inviter" (the board owner / inviting rep) or "by_email" (the explicit sender below).
senderobject{ "id": <number>, "email": "<string>" }. Used when sender_type is "by_email".
Id fields are numbers. form_id, privacy_message_id, email_template_id, and sender.id are typed as numbers, and a quoted value fails with expected number, got string.
Where do form ids come from? List and search your organization’s forms with GET /v1/forms. You can also reuse a form_id a template already carries in its /describe output.

Example: banner with primary and secondary CTA

"cta": {
  "text": "Download the report",
  "type": "flz-primary",
  "action": {
    "type": "open_content_item",
    "open_content_item": {
      "flz_item_id": { "content_center_item_id": 775972 },
      "design": "sidebar",
      "allow_download": true
    }
  }
},
"secondary_cta": {
  "text": "Schedule a Demo",
  "type": "flz-secondary",
  "action": {
    "type": "open_url",
    "open_url": {
      "url": "https://example.com/demo",
      "open_in_new_window": true
    }
  }
}

Example: header navigation with mixed actions

"navigation": {
  "items": [
    {
      "text": "About Us",
      "type": "flz-link",
      "action": {
        "type": "anchor",
        "anchor": { "hash": "about" }
      }
    },
    {
      "text": "Contact",
      "type": "flz-link",
      "action": {
        "type": "form",
        "form": { "form_id": "86102" }
      }
    },
    {
      "text": "Share",
      "type": "flz-link",
      "action": {
        "type": "share",
        "share": { "privacy_message_id": "11869" }
      }
    }
  ]
}

Where CTAs appear

LocationFieldsNotes
Header widgetctaSingle CTA in the top bar. Controlled by visibility.cta.
Header navigationnavigation.items[]Each item is a CTA object. Typically uses flz-link style. Controlled by visibility.navigation.
Banner widgetcta, secondary_ctaPrimary and secondary buttons. Controlled by visibility.cta, visibility.secondary_cta, and the parent visibility.showButtons toggle.
Body sectionscta, secondary_ctaSupported by text, image, carousel, and other body section widgets.
Carousel columnsrepeatable.columns[].ctaPer-column CTA. May omit the type field (inherits the column default style).
Visibility gates. Even if a CTA is fully configured, it won't render unless its visibility flag is true. Always check and set the matching visibility key when adding or enabling a CTA. For banners, the showButtons flag is a parent toggle that must also be true for either button to appear.

Configuring images #

Many widgets expose editable image fields. The API accepts any public image URL or base64-encoded data URI and automatically hosts it on the Folloze CDN — no separate upload step is required.

Rewritten 2026-09-16. Earlier revisions of this page described image_config as read-only and listed maxWidth, maxHeight, artisticFilter, and sharpness. None of those fields exist. image_config is writable, against a strict allowlist that differs per image kind, and the sizing hint now lives in a separate read-only cover object. Payloads built from the old field list are rejected with 422.

The three image kinds

Images are not one shape. Which config keys are accepted depends on where the image sits, and sending a key the location does not accept is a 422, not a silent ignore.

KindWhere it appearsConfig typecropfitshape
Editable imageWidget content — image-section, single-item, side-by-side sides, video preview images, event view imagesImageConfigyesyeswritable
Icon imagerepeatable.columns[].image on promo columns and carousels, repeatable.items[].image on Q&A, tabs.items[].icon on multi-tabIconImageConfignoyesread-only
Ribbon backgroundribbon.background / ribbon.backgroundMobile with type: "image"RibbonImageConfigyesnowritable

Always confirm against the section’s own schema — see Reading the authoritative schema.

Image object

Editable images and icon images share the same envelope:

FieldTypeDescription
urlstringrequiredPublic URL (https://...), base64 data URI (data:image/png;base64,...), or an existing Folloze CDN URL. Re-hosted on write and replaced with the hosted URL.
image_configobjectoptionalTransformations. Allowed keys depend on the image kind — see above.
coverobjectread-onlyMinimum size the image must cover. See cover.
altstringoptionalAlt text for accessibility.
linkstringoptionalClick-through URL — if set, the image becomes a clickable link.
"image": {
  "url": "https://cdn.example.com/hero-banner.jpg",
  "image_config": { "shape": "none", "fit": "cover", "flipX": false, "flipY": false, "filter": "none" },
  "alt": "Product dashboard screenshot",
  "link": "https://example.com/product-tour"
}

Write semantics: what a new url resets

This is the single most common source of surprise when round-tripping /describe output.

You sendWhat happens
The same url it already hasNothing changes. The existing config is preserved.
A new url on an editable imageshape, crop, flipX, flipY, filter, and tint are cleared. fit is unchanged. A crop sent in the same request is applied to the new image.
A new url on an icon imageflipX, flipY, filter, and tint are cleared. fit is unchanged. (Icons have no crop, and shape is not writable here.)

So to swap an image and keep its framing, send the new url and the image_config you want in the same request. A follow-up call will not re-apply the old settings — they are already gone.

Accepted input formats

InputWhat happens
Public URL (https://cdn.example.com/photo.jpg)Downloaded and uploaded to Folloze CDN. The response returns the hosted CDN URL.
Base64 data URI (data:image/png;base64,iVBOR...)Supported. The image is decoded, stored as a file, and the field comes back holding the hosted URL — not the data URI you sent. Keep an eye on payload size: a data URI inflates the request by roughly a third over the raw file, and a create call carrying several is easy to make needlessly large.
Existing Folloze CDN URL (https://images.folloze.com/image/upload/...)Normalized (version stripped) and passed through — not re-uploaded.
Automatic deduplication. The API deduplicates uploads by content fingerprint. Sending the same image URL twice does not create a second copy on the CDN.
Unfetchable images return 422. If Folloze cannot fetch the URL you supply (404, auth wall, hotlink protection, unsupported type), the whole call fails with 422 naming the field. Use a directly fetchable, publicly reachable URL.

image_config fields

FieldTypeKindsDescription
shapestringall"square", "rectangle", "circle", or "none". Frames the image. Read-only on icon images — the group’s frame is set by repeatable.image.image_shape on the section instead.
cropobjecteditable, ribbonPixel rectangle in the source image — see crop. Not accepted on icon images.
fitstringeditable, icon"cover" or "contain". How the image fills its frame. Not accepted on ribbon backgrounds — use the ribbon’s own size field.
flipXbooleanallFlip horizontally.
flipYbooleanallFlip vertically.
filterstringallNamed artistic filter — see the value list below. Replaces the old artisticFilter.
tintobjectallColour overlay: { "color": "#hex", "alpha": <0-100> }. alpha is 0–100, not 0–1. Omitted when unused.

There is no maxWidth, maxHeight, sharpness, or artisticFilter. Sending any of them fails with unknown field '<name>' (expected one of [...]).

filter values

none, al_dente, athena, audrey, aurora, daguerre, eucalyptus, hairspray, hokusai, peacock, primavera, quartz, incognito, red_rock, sizzle, fes, linen, refresh, sonnet, ukulele, frost, zorro

crop

A pixel rectangle in the source image, with exactly four keys:

FieldTypeDescription
xnumberLeft offset, in source pixels.
ynumberTop offset, in source pixels.
widthnumberWidth, in source pixels.
heightnumberHeight, in source pixels.
"crop": { "x": 136, "y": 45, "width": 2920, "height": 2101 }

There are no aspect, radius, crop, or unit sub-fields — earlier revisions of this page listed them in error.

The crop must already match the shape’s ratio. square and circle are 1:1, rectangle is 16:9, none is free. A crop that does not match is rejected, not re-framed, and it must fit inside the image. Send {} to clear a crop. Changing shape on its own re-fits the crop already stored.

cover (read-only)

cover states the minimum size the image needs to fill its slot without upscaling:

"cover": { "width": 1920 }

"cover": { "width": 240, "height": 240 }
Round-trip it unchanged, or leave it out. cover is read-only. Sending a different value is rejected. It is a hint for choosing a source image — pick one at least this large — not a resize instruction. This field replaced the maxWidth / maxHeight pair that older integrations sent.

Example: ribbon background with tint and crop

"ribbon": {
  "background": {
    "type": "image",
    "url": "https://images.folloze.com/image/upload/hpifscdihf1mw21yytsy.jpg",
    "image_config": {
      "shape": "none",
      "flipX": false,
      "flipY": false,
      "filter": "none",
      "tint": { "color": "#081d4d", "alpha": 78 },
      "crop": { "x": 1, "y": 154, "width": 1723, "height": 672 }
    },
    "cover": { "width": 1920 }
  },
  "optimizedForMobile": false
}

Sizing repeatable images: image_shape and image_size

Icon images inside a repeatable group are framed by the group, not by the individual image:

FieldTypeValues
repeatable.image.image_shapestring"circle", "square", "rectangle"
repeatable.image.image_sizestring"small", "medium", "large"
"repeatable": {
  "image": { "image_shape": "circle", "image_size": "medium" },
  "columns": [
    {
      "title": "Faster onboarding",
      "image": { "url": "https://.../rocket.png", "alt": "Rocket" }
    }
    /* ... */
  ]
}
Renamed: icon is now image in repeatable groups. As of 2026-10-19, repeatable columns (repeatable.columns[] on promo columns, promo carousel and running carousel) take image instead of icon. Allowed column keys are title, subtitle, paragraph, image, cta, back, visibility; the flip side is flip.back.image. Q&A rows live at repeatable.items[] (not columns) with keys label, title, paragraph, image, side_media, cta, visibility. The group setting repeatable.icon.{icon_shape, icon_size} is now repeatable.image.{image_shape, image_size}, with the same values. Sending the old key fails:
422 unknown field 'icon' (expected one of [title, subtitle, paragraph, image, cta, back, visibility])
/describe returns the new keys. The image object and its image_config allowlist are unchanged: these are still icon images (no crop, shape read-only). Exception: multi-tab is not renamed — tabs.items[].icon still describes as icon.
Known issue: /describe can return a legacy image_size. Some boards and templates carry a stored value of "medium_large" or "small_medium" under the new key, which the create/update validator correctly rejects (expected one of [small, medium, large]). A verbatim clone of /describe output will fail on such a section. Before posting back, map "medium_large" to "large" and "small_medium" to "medium". Tracked for a data fix.

Where images appear

The exact paths depend on the widget — always read /describe for your template to see the actual structure.

WidgetImage field pathsKind
Image sectionimageeditable
Single itemimageeditable
Video sectionpreview_image.imageeditable
Simulive event / Zoomviews.pre.image, views.during.preview_image.image, views.post.placeholder.imageeditable
Side by sideleft/right.image.image, left/right.video.preview_image.imageeditable
Multi-tabtabs.items[].left/right.image.image, tabs.items[].left/right.video.preview_image.imageeditable
Promo columnsrepeatable.columns[].imageicon
Carousel (promo)repeatable.columns[].imageicon
Running carouselrepeatable.columns[].imageicon
Multi-tab columnstabs.items[].icon, tabs.items[].columns[].iconicon
Q&A sectionrepeatable.items[].imageicon
Q&A sectionrepeatable.items[].side_media.image.image, repeatable.items[].side_media.video.preview_image.imageeditable
Headerprimary_logo.image, secondary_logo.imageeditable
Ribbon (background)ribbon.background, ribbon.backgroundMobile (with type: "image")ribbon
Schema-driven. The table above reflects the current set of widgets with image support. New widgets may add image fields at any time — always check /describe, or the section schema, for the authoritative list.

Setting items on content sections #

You can specify which content items appear in a section at board-creation time using the item_id field type. The API accepts two value formats for every item_id field:

FormatExampleWhen to use
Plain integer774620Item already exists on the source template board
Content center reference{ "content_center_item_id": 774620 }Item exists in the org’s Content Center but is not on the template. The API auto-adds it to the new board during creation.
Bridges both APIs. When you use the content_center_item_id object form, the API automatically adds the item to the new board — no separate POST /v1/board_items step needed. The item must exist in your organization’s Content Center; create it first with POST /v1/items if needed.

Supported widgets

WidgetField pathCardinalityStatus
single-itemsources.flz_item_idSingle itemSupported
video-sectionsources.flz_item_idSingle itemSupported
side-by-sideleft.video.flz_item_id / right.video.flz_item_idSingle item per sideSupported
multi-tabtabs[].left.video.flz_item_id / tabs[].right.video.flz_item_idSingle item per tab sideSupported
qa-sectionrepeatable.items[].side_media.video.flz_item_idSingle item per Q&A rowSupported
content-carouselsources.flz_item_idsMultiple items (array)Bug — currently accepts only a single value; array support pending fix
content-promotedsources.flz_item_idsMultiple items (array)Bug — same as carousel

Not yet supported

The following content widgets do not expose item sources in their API schema. Items on these sections can only be set through the Folloze UI or via POST /v1/board_items after board creation.

Example: single-item widget

"widgets": {
  "single-item": {
    "title": "Featured case study",
    "subtitle": "See how Acme Corp transformed their pipeline",
    "sources": {
      "flz_item_id": { "content_center_item_id": 774620 }
    }
  }
}

Example: side-by-side with video item

"widgets": {
  "side-by-side": {
    "title": "Product overview",
    "left": {
      "paragraph": { "title": "...", "subtitle": "..." },
      "video": {
        "preview_image": { "image": { "url": "https://..." } },
        "flz_item_id": { "content_center_item_id": 775972 }
      }
    },
    "right": {
      "image": { "image": { "url": "https://..." } },
      "paragraph": { "title": "...", "subtitle": "..." }
    }
  }
}

CTA: open a content item #

The open_content_item action type on any CTA opens a content item in the board’s item viewer. The flz_item_id inside open_content_item accepts the same two formats as the sources fields — plain integer or content_center_item_id object.

CTA object with open_content_item

"cta": {
  "text": "View case study",
  "type": "flz-primary",
  "action": {
    "type": "open_content_item",
    "open_content_item": {
      "flz_item_id": { "content_center_item_id": 775972 },
      "design": "sidebar",
      "allow_download": true
    }
  }
}
FieldTypeDescription
open_content_item.flz_item_idinteger | objectThe content item to open. Pass a plain integer (existing board item) or { "content_center_item_id": <id> } (auto-added from Content Center).
open_content_item.designstringOptional. Viewer layout style (e.g. "sidebar").
open_content_item.allow_downloadbooleanOptional. Whether the visitor can download the content item.

This action is available on every CTA across all widget types — banners, split banners, side-by-side, promo columns, Q&A, single items, etc. See Configuring CTAs for the full list of action types.


Event sections (Zoom / Simulive) #

Zoom and Simulive event sections let you embed a live or pre-recorded video experience directly into a board — with scheduling, state-specific images, and a registration CTA. Both widgets share the same schema; they differ only in the video source field.

Zoom vs. Simulive

Widget keySource fieldValueUse case
zoomsources.meetingUrlZoom meeting or webinar URLLive meetings & webinars
simulive-eventsources.videoUrlPre-recorded video URLOn-demand / simulated-live playback at a scheduled time

Widget fields

FieldTypeDescription
namestringStructured event name — displayed consistently across pre/during/post views. Separate from the rich-text title.
titlestringEvent headline (Quill HTML)
subtitlestringEvent description (Quill HTML)
sources.meetingUrlstringZoom only. Full Zoom meeting/webinar URL
sources.videoUrlstringSimulive only. Pre-recorded video URL
scheduling.startTimestring (ISO 8601)Event start time, e.g. "2026-07-15T14:00:00Z"
scheduling.endTimestring (ISO 8601)Event end time, e.g. "2026-07-15T15:00:00Z"
scheduling.timezonestring (IANA)Timezone for displaying event times to the viewer. Must be one of the supported values.
ctaobjectRegistration / action button — see Configuring CTAs
views.pre.imageimage objectBanner shown before the event starts
views.during.preview_image.imageimage objectPreview image shown during the event (behind the video player)
views.post.placeholder.imageimage objectImage shown after the event ends
Scheduling note: The API accepts ISO 8601 date strings (e.g. "2026-07-15T14:00:00Z"), and returns ISO strings when you read the widget back via the Describe endpoint. Set scheduling.timezone to control how the event time is rendered for visitors.

Supported timezones

The scheduling.timezone field accepts the following IANA timezone identifiers:

Pacific/Honolulu, America/Anchorage, America/Los_Angeles, America/Denver, America/Chicago, America/New_York, America/Halifax, Europe/London, Europe/Berlin, Europe/Athens, Europe/Moscow, Asia/Jerusalem, Australia/Perth, Australia/Adelaide, Australia/Sydney

Image objects

Each image slot (views.pre.image, views.during.preview_image.image, views.post.placeholder.image) accepts:

FieldTypeDescription
urlstringImage URL (Folloze CDN or any public URL)
altstringAlt text for accessibility
linkstringOptional click-through URL

Full example: Zoom event section

{
  "section_id": "board:default:s_abc12345",
  "widgets": {
    "zoom": {
      "name": "OT Security for Manufacturing",
      "title": "Live webinar: OT security for manufacturing",
      "subtitle": "Join our experts for a deep dive into securing operational technology",
      "sources": {
        "meetingUrl": "https://zoom.us/j/1234567890"
      },
      "scheduling": {
        "startTime": "2026-07-15T14:00:00Z",
        "endTime": "2026-07-15T15:00:00Z",
        "timezone": "America/New_York"
      },
      "cta": {
        "text": "Register now",
        "type": "flz-primary",
        "action": {
          "type": "form",
          "form": { "form_id": "172817" }
        }
      },
      "views": {
        "pre": {
          "image": {
            "url": "https://images.folloze.com/image/upload/v.../pre-event-banner.jpg",
            "alt": "Webinar pre-event banner"
          }
        },
        "during": {
          "preview_image": {
            "image": {
              "url": "https://images.folloze.com/image/upload/v.../during-preview.jpg",
              "alt": "Webinar in progress"
            }
          }
        },
        "post": {
          "placeholder": {
            "image": {
              "url": "https://images.folloze.com/image/upload/v.../post-event-thanks.jpg",
              "alt": "Thank you for attending"
            }
          }
        }
      }
    }
  }
}

Full example: Simulive event section

{
  "section_id": "board:default:s_def67890",
  "widgets": {
    "simulive-event": {
      "name": "Platform Walkthrough",
      "title": "On-demand demo: Platform walkthrough",
      "subtitle": "Watch our 30-minute product tour at the scheduled time",
      "sources": {
        "videoUrl": "https://videos.example.com/demo-recording.mp4"
      },
      "scheduling": {
        "startTime": "2026-08-01T10:00:00Z",
        "endTime": "2026-08-01T10:30:00Z",
        "timezone": "Europe/London"
      },
      "cta": {
        "text": "Reserve your spot",
        "type": "flz-primary",
        "action": {
          "type": "form",
          "form": { "form_id": "183920" }
        }
      },
      "views": {
        "pre": {
          "image": { "url": "https://images.folloze.com/image/upload/v.../simulive-pre.jpg" }
        },
        "during": {
          "preview_image": {
            "image": { "url": "https://images.folloze.com/image/upload/v.../simulive-during.jpg" }
          }
        },
        "post": {
          "placeholder": {
            "image": { "url": "https://images.folloze.com/image/upload/v.../simulive-post.jpg" }
          }
        }
      }
    }
  }
}
Tip: The cta on event sections is commonly set to "form" action type to capture registrations. You can also use "open_url" to link to an external registration page, or "send_email_clicked" for direct contact.

Organization media library #

Browse your organization's media library to find image URLs for board creation payloads — logos, banners, backgrounds, icons, and more. This endpoint uses the same Basic auth and base URL as the rest of the Board Creation API.

GET/v1/organization_media

Query parameters

ParameterTypeRequiredDescription
category[]string (repeatable)NoFilter by one or more categories. Repeat the parameter for multiple: category[]=logos&category[]=banners
sort_colstringNoColumn to sort by (e.g. "created_at", "name")
sort_directionstringNo"asc" or "desc"
pageintegerNoPage number (default: 1)
per_pageintegerNoResults per page (default: 20)

Image categories

Category valueDescription
bannersBanner / hero images
mobile_bannersMobile-optimized banner images
thumbnailsThumbnail images
iconsIcons and small graphics
logosCompany and brand logos
videosVideo thumbnails / poster images
imagesGeneral-purpose images
avatarsProfile / avatar images

Example request

curl -s "https://content-api.folloze.com/v1/organization_media?category[]=logos&category[]=banners&per_page=10&page=1&sort_col=created_at&sort_direction=desc" \
  -H "Authorization: Basic $BASE64_CREDENTIALS"

Example: fetch all images (no category filter)

curl -s "https://content-api.folloze.com/v1/organization_media?per_page=100&page=1" \
  -H "Authorization: Basic $BASE64_CREDENTIALS"

Example response

{
  "media": [
    {
      "id": 170092,
      "name": "Cloud Network Logo",
      "url": "https://images.folloze.com/image/upload/v1770730346/kpo6sywpsdpcqwi8esdq.png",
      "category": "logos",
      "created_at": "2026-03-08T13:35:19.119Z",
      "updated_at": "2026-03-08T13:35:19.119Z",
      "uploader": null
    },
    {
      "id": 169988,
      "name": "Banner Image",
      "url": "https://images.folloze.com/image/upload/v1759827103/qqtposangcjdxfymv72k.jpg",
      "category": "banners",
      "created_at": "2026-03-08T13:33:18.807Z",
      "updated_at": "2026-03-08T13:33:18.807Z",
      "uploader": null
    }
  ],
  "pagination": {
    "total_items": 47,
    "total_pages": 24,
    "current_page": 1,
    "per_page": 2
  }
}

Response fields

FieldTypeDescription
mediaarrayArray of image objects
media[].idintegerImage ID
media[].namestringImage name
media[].urlstringFull CDN URL — use directly in any image field on your board payload
media[].categorystringImage category (e.g. "logos", "banners")
media[].created_atstring (ISO 8601)Upload timestamp
media[].updated_atstring (ISO 8601)Last update timestamp
media[].uploaderstring | nullFull name of the user who uploaded the image
pagination.total_itemsintegerTotal number of images matching the query
pagination.total_pagesintegerTotal number of pages
pagination.current_pageintegerCurrent page number
pagination.per_pageintegerResults per page

List forms #

GET/v1/forms

Lists and searches the forms in your organization, so you can resolve a form_id for the form and registration CTA actions without opening the Folloze app.

Organization forms only. This returns forms that are available in Designer and reusable across boards. Forms created inside a single board for that board’s own use are deliberately excluded — take those ids from the board’s /describe output instead.

Query parameters

ParameterTypeDescription
querystringFree-text search over form names.
form_typestring | arrayFilter by form type. Repeat the parameter to pass several. One of classic, external, marketo, eloqua, hubspot.
sort_colstringcreated_at (default), name, updated_at, or available_in_designer.
sort_directionstringasc or desc.
pageintegerPage number, starting at 1. Default 1.
per_pageintegerResults per page. Default 24, maximum 100.

Example request

curl -G "https://content-api.folloze.com/v1/forms" \
  --data-urlencode "query=demo" \
  --data-urlencode "form_type=classic" \
  --data-urlencode "sort_col=name" \
  --data-urlencode "sort_direction=asc" \
  --data-urlencode "per_page=10" \
  -H "Authorization: Basic <your_api_token>" \
  -H "User-Agent: Folloze" \
  -H "Folloze-Initiator: Folloze"

Example response

{
  "forms": [
    { "id": 12634, "name": "Book a demo",        "form_type": "classic",  "updated_at": "2026-08-14T09:22:11.000Z" },
    { "id": 12871, "name": "Demo request (EMEA)", "form_type": "marketo", "updated_at": "2026-09-02T16:40:03.000Z" }
  ],
  "pagination": {
    "total_items": 2,
    "total_pages": 1,
    "current_page": 1,
    "per_page": 10
  }
}

Response fields

FieldTypeDescription
forms[].idintegerThe form_id to use in a CTA action.
forms[].namestringForm name as it appears in Folloze.
forms[].form_typestringOne of classic, external, marketo, eloqua, hubspot.
forms[].updated_atstringISO 8601 timestamp of the last change.
pagination.total_itemsintegerTotal forms matching the filters.
pagination.total_pagesintegerTotal number of pages.
pagination.current_pageintegerCurrent page number.
pagination.per_pageintegerResults per page.

Errors

StatusWhen
400page is not an integer greater than 0, per_page is outside 1–100, or form_type is not one of the accepted values. The body names the offending parameter.
500Unexpected failure fetching forms.

List templates #

GET/v1/board_templates

Returns every template board in your organization with its id, name, and description. Use this to discover which templates are available before fetching schemas or creating boards — especially helpful when you don't already know the template ids.

Example request

curl https://content-api.folloze.com/v1/board_templates \
  -H "Authorization: Basic $FOLLOZE_API_TOKEN"
const res = await fetch(
  "https://content-api.folloze.com/v1/board_templates",
  { headers: { Authorization: `Basic ${FOLLOZE_API_TOKEN}` } }
);
const templates = await res.json();
import requests
res = requests.get(
    "https://content-api.folloze.com/v1/board_templates",
    headers={"Authorization": f"Basic {FOLLOZE_API_TOKEN}"},
)
templates = res.json()

Example response

[
  {
    "id": 178297,
    "name": "CSM: Account Resource Center",
    "description": "A customer-success hub for sharing onboarding guides, success plans, and key contacts with each account. Use as the home base for ongoing customer engagement. #csm #customer-success"
  },
  {
    "id": 155459,
    "name": "[Template] Digital Sales Room",
    "description": "Digital Sales room for Folloze - \n\nHow to use:\n\nWithin the content section there are categories labelled \"Hidden\" where you an pull in the most used content straight into the actual categories. \n\n #Template #sales"
  },
  {
    "id": 165922,
    "name": "[Template] GeneratorAI",
    "description": "Pre-wired template for boards generated by AI pipelines — every section's titles, subtitles, and CTAs are designed to be filled programmatically. Pair with a content pipeline that calls POST /v1/boards. #generative #template"
  },
  {
    "id": 152644,
    "name": "[BuyEx 3.0 Template] ABM 1:1",
    "description": "1:1 personalized buyer experience for ABM plays — branded for a single target account, with sections for tailored value props, case studies, and a curated content library. #abm #buyex"
  }
]

Response fields

FieldTypeDescription
idintegerTemplate id — pass this as template_board_id on POST /v1/boards, or as the :id path parameter on GET /v1/board_templates/:id/describe.
namestringDisplay name of the template as set by the template owner in Folloze.
descriptionstringTemplate description (may be empty). Often used to capture intent, instructions, or hashtags — useful for filtering templates client-side.

Errors

StatusWhen
401Authentication is missing or invalid.
403Feature board_creation_api is disabled for the organization.

Describe template / board #

GET/v1/board_templates/:id/describe
GET/v1/boards/:id/describe

Returns the full structure of a template or any existing board: every body section (with the API schema of its widgets pre-populated with the current content), plus the header and footer, all in the format used for creation. Useful for “round-tripping” — read it, modify it, and post it back to create a new board.

Two endpoints, same response. Use /v1/board_templates/:id/describe for template boards, or /v1/boards/:id/describe for any board (including non-templates). The response format is identical — the only difference is the source.

The response is keyed by page. Every board has a main_page. Boards that include a registration page also have a registration_page key with its own sections, header, and footer. Alongside the pages, the top level carries theme_id — the id of the board theme applied to this board or template (null when none is set).

Example response

{
  "theme_id": 777,
  "main_page": {
    "header": {
      "section_id": "board:default:s_f316586e",
      "type": "header",
      "name": "Header - original POC",
    "widgets": {
      "header": {
        "primary_logo": {
          "image": {
            "url": "https://images.folloze.com/image/upload/v1759506309/vuo2cfwtclkhewhd5ryb.png"
          }
        },
        "secondary_logo": {
          "image": {
            "url": "https://images.folloze.com/image/upload/v1776690426/aqmsxd15kdpeoj3pm3vi.svg"
          }
        },
        "cta": {
          "text": "Contact Us",
          "type": "flz-secondary",
          "action": {
            "type": "open_url",
            "open_url": {
              "url": ""
            }
          }
        },
        "color": {
          "tag_line": "#000000"
        },
        "visibility": {
          "primary_logo": true,
          "show_logos": true,
          "show_symbols": false,
          "secondary_logo": true,
          "tag_line": false,
          "cta": false,
          "share_button": false,
          "navigation": false
        },
        "navigation": {}
      },
      "banner": {
        "title": "<div style=\"--fz-marker-color: var(--fz-color-neutral-0); text-align: center;\" class=\"ql-style-heading2\"><span style=\"color: var(--fz-color-primary-1);\">Global. Simple. Unified. </span></div><div style=\"--fz-marker-color: var(--fz-color-neutral-0); text-align: center;\" class=\"ql-style-heading2\"><span style=\"color: var(--fz-color-primary-1);\">It’s the Power of One</span></div><div style=\"--fz-marker-color: var(--fz-color-neutral-0); text-align: center;\" class=\"ql-style-heading3\"><span style=\"text-align: left;\"><br></span></div><div style=\"--fz-marker-color: var(--fz-color-neutral-0); text-align: center;\" class=\"ql-style-heading3\"><span style=\"text-align: left; color: var(--fz-color-primary-1);\">How JLL will empower </span></div><div style=\"--fz-marker-color: var(--fz-color-neutral-0); text-align: center;\" class=\"ql-style-heading3\"><span style=\"text-align: left; color: var(--fz-color-primary-1);\">[insert target’s sector]</span><span style=\"text-align: left;\"> </span></div><div style=\"--fz-marker-color: var(--fz-color-neutral-0); text-align: center;\" class=\"ql-style-heading3\"><span style=\"color: var(--fz-color-primary-1);\"><br></span></div><div style=\"--fz-marker-color: var(--fz-color-neutral-0); text-align: center;\" class=\"ql-style-heading3\"><br></div>",
        "subtitle": "<div style=\"text-align: center;\" class=\"ql-style-heading4\"><span style=\"color: var(--fz-color-primary-1);\">Enhance productivity, services and employee experiences with data center and workspace management solutions from JLL.</span></div>",
        "cta": {
          "text": "Read your business case now",
          "type": "flz-primary",
          "action": {
            "type": "open_content_item",
            "open_content_item": {}
          }
        },
        "secondary_cta": {
          "text": "Schedule a Demo",
          "type": "flz-secondary",
          "action": {
            "type": "open_url",
            "open_url": {
              "url": "",
              "open_in_new_window": true
            }
          }
        }
      }
    },
    "ribbon": {
      "background": { "type": "color", "value": "background: #ffffff" }
    }
  },
    "sections": [ /* array of section objects */ ],
    "footer_id": 789
  },
  "registration_page": {
    "sections": [
      {
        "section_id": "board:registration:s_reg001",
        "type": "body",
        "name": "Registration",
        "anchor": "signup",
        "widgets": { /* widget schema with current content */ },
        "ribbon": {
          "background": { "type": "color", "value": "background: var(--fz-color-neutral-0)" }
        }
      }
    ]
  }
}
Registration page is optional. The registration_page key only appears if the template/board has a registration page configured. Boards without one return only main_page.
theme_id. Added 2026-08-20. The id of the board theme the template or board renders with, or null if no theme is applied. It is read-only here — POST /v1/boards takes the theme from the template or source board, so there is nothing to send back. Use it to tell which templates share a theme, or to look the theme up in the Folloze app.
section_id values are prefixed. Since 2026-08-20 every section_id in this response is a canonical id of the form board:<page>:<section>. Pass the string through unchanged when you post it back to POST /v1/boards or PATCH /v1/boards/:board_id. See Section & widget identity.
Header anatomy varies by template. Folloze ships several header types (e.g. Header with navigation, Header with 2 logos, Header - original POC), and each one defines its own set of widgets. Don't assume a fixed shape — the exact widgets keys, fields, and visibility flags depend on the header type your template uses. Always read the actual /describe response for your template and round-trip what you receive (including the name, the full visibility map, and any rich-text HTML in titles/subtitles) when posting to POST /v1/boards.

Variation: header with two logos and inline navigation

Different header types return different widget keys and fields. For example, a template using the Header with 2 logos type returns a widgets.header with a populated navigation.items array (instead of an empty navigation: {}) and no banner widget at all:

{
  "theme_id": 777,
  "main_page": {
    "header": {
      "section_id": "board:default:s_27da2d1a",
      "type": "header",
      "name": "Header with 2 logos",
    "widgets": {
      "header": {
        "primary_logo": {
          "image": {
            "url": "https://images.folloze.com/image/upload/v1750030731/d7ajbarxztndszv8ywi8.png"
          }
        },
        "secondary_logo": {
          "image": {
            "url": "",
            "link": "",
            "alt": ""
          }
        },
        "cta": {
          "text": "Contact Us",
          "type": "flz-secondary",
          "action": {
            "type": "open_url",
            "open_url": {
              "url": ""
            }
          }
        },
        "color": {
          "tag_line": "#000000",
          "symbol": "var(--fz-color-neutral-4)"
        },
        "visibility": {
          "primary_logo": true,
          "show_logos": true,
          "show_symbols": true,
          "secondary_logo": false,
          "tag_line": false,
          "cta": false,
          "share_button": false,
          "navigation": true
        },
        "navigation": {
          "items": [
            { "text": "Solutions",       "type": "flz-link", "action": { "type": "open_url", "open_url": {} } },
            { "text": "Why us",          "type": "flz-link", "action": { "type": "open_url", "open_url": {} } },
            { "text": "Success stories", "type": "flz-link", "action": { "type": "open_url", "open_url": {} } },
            { "text": "Product tour",    "type": "flz-link", "action": { "type": "open_url", "open_url": {} } },
            { "text": "Resources",       "type": "flz-link", "action": { "type": "open_url", "open_url": {} } }
          ]
        }
      }
    }
    }
  }
}

Search boards #

GET/v1/boards

Search and list boards in your organization. Supports filtering by name, status, and owner — based on the same search functionality available in the Folloze UI.

Query parameters

ParameterTypeRequiredDescription
searchstringNoSearch boards by name (partial match)
activation_stateintegerNoFilter by board status: 1 = online, 0 = draft
ownerintegerNoFilter by owner user id
pageintegerNoPage number (default: 1)
per_pageintegerNoResults per page (default: 50)

Example request

curl -s "https://content-api.folloze.com/v1/boards?search=Acme&activation_state=1&page=1&per_page=10" \
  -H "Authorization: Basic $BASE64_CREDENTIALS"

Example response

{
  "boards": [
    {
      "id": 247054,
      "name": "Personalized Hub for Acme Corp",
      "description": null,
      "status": "Online",
      "board_url": "https://acme.boards.folloze.com/eee17a",
      "image_url": "https://images.folloze.com/image/upload/v.../hero.png",
      "owner": {
        "id": 1999626,
        "full_name": "Jane Smith",
        "email": "jane@acme.com"
      },
      "hashtags": [],
      "created_at": "2026-07-06T08:27:35.511Z",
      "updated_at": "2026-07-06T08:28:12.797Z"
    }
  ],
  "pagination": {
    "total_items": 1,
    "total_pages": 1,
    "current_page": 1,
    "per_page": 10
  }
}

Response fields

FieldTypeDescription
boardsarrayArray of board objects
boards[].idintegerBoard id — use with GET /v1/boards/:id/describe or source_board_id on POST /v1/boards
boards[].namestringBoard display name
boards[].descriptionstring | nullBoard description
boards[].statusstring"Online" or "Draft"
boards[].board_urlstringPublic board URL
boards[].image_urlstringBoard thumbnail / hero image URL
boards[].ownerobjectBoard owner with id, full_name, and email
boards[].hashtagsstring[]Board tags
boards[].created_atstring (ISO 8601)Creation timestamp
boards[].updated_atstring (ISO 8601)Last update timestamp
pagination.total_itemsintegerTotal boards matching the query
pagination.total_pagesintegerTotal pages
pagination.current_pageintegerCurrent page
pagination.per_pageintegerResults per page

Create a board #

POST/v1/boards

Creates a new board in the organization, copying the source board's structure and applying your content. The source can be a template board (template_board_id) or any existing board (source_board_id).

Request body

FieldTypeDescription
template_board_idnumberrequired*Id of the template board to use. Required unless source_board_id is provided.
source_board_idnumberrequired*Id of any existing board (non-template) to use as the source. Use GET /v1/boards/:id/describe to inspect its structure first. Required unless template_board_id is provided.
main_pageobjectrequired*Main page content. Contains sections, header, and footer_id (see page object). Required unless top-level sections is provided.
registration_pageobjectoptionalRegistration page content. Same shape as main_page (page object). Only valid when the template/source board has a registration page — returns 422 otherwise.
sectionsarrayrequired*Legacy format. Array of section objects for the main page. Equivalent to main_page.sections. Required unless main_page is provided.
board_namestringoptionalDisplay name. Defaults to "Board from {template name}".
headerobjectoptionalLegacy format. Header overrides for the main page. Equivalent to main_page.header. The shape depends on the header type your template uses — fetch /describe to see the exact structure.
footer_idnumberoptionalLegacy format. Footer id for the main page. Equivalent to main_page.footer_id.
owner_emailstringoptionalEmail of an existing Folloze user to assign as the board owner. Must match a user in the organization. If omitted, the board is owned by the first admin of the organization.
tagsstring[]optionalBoard tags to apply. New tags are created on demand; existing tags are reused.
statestringoptionalInitial board state. Accepted values: "online" (published) or "draft". Defaults to "draft".
channelsarrayoptionalChannels to add the new board to. Each entry is { "name": "..." } — matched by exact name (case-sensitive, no whitespace trimming); the board joins the existing channel if a match is found, otherwise a new channel is created with this name. See Channels.
slugstringoptionalVanity URL slug for the board. The board will be accessible at https://<domain>.boards.folloze.com/<slug>. Must be unique across the organization. Returns 422 if the slug is already taken.
settingsobjectoptionalBoard settings. Currently supports access for access control configuration. See Access settings.
Legacy vs. multi-page format. You can pass sections, header, and footer_id at the top level (legacy format) or inside a main_page object. Both are equivalent for the main page. Use main_page + registration_page when your template has a registration page and you want to customize it.

Page object

FieldTypeDescription
sectionsarrayrequiredArray of section objects. Sections in the template page but missing here are removed from the new board's page.
headerobjectoptionalHeader overrides for this page. Shape depends on the header type — fetch /describe to see the exact structure.
footer_idnumberoptionalId of an existing footer CampaignElement in the organization.

Section object

FieldTypeDescription
section_idstringrequiredCanonical id from GET /describe, in the form board:<page>:<section> (e.g. "board:default:s_abc123"). Pass it back exactly as received — bare ids and ids naming a different page are not resolved. Sections without a section_id are not added to the new board. See Section & widget identity.
typestringoptionalSection type, typically "body". Header / footer are managed via header and footer_id, not here.
namestringoptionalThe section's design-name as it appears in Designer (e.g. "Hero Banner", "Key Point - 50/50"). Informational — sections are matched by section_id, not by name, and names are not unique.
anchorstringoptionalAnchor name for in-page navigation (e.g. "challenges"). A header navigation item with action: { "type": "anchor", "anchor": { "hash": "challenges" } } scrolls here.
ribbonobjectoptionalThe section's background layer. See Ribbons. Sections without a background omit the key entirely.

Channels

Pass each channel as { "name": "…" }. The name is matched exactly (case-sensitive, no whitespace trimming) against existing channels in the organization — the board joins the existing channel if a match is found; otherwise a new channel is created with this name.

{
  "channels": [
    { "name": "Q4 launch" },
    { "name": "Acme account" }
  ]
}
FieldTypeDescription
namestringChannel name. Matched exactly against existing channels; created if no match.

Access settings

The settings.access object controls who can view the board. Four access types are supported:

TypeDescription
open_to_allAnyone with the link can view. Optionally enable token-based access.
passwordVisitors must enter a password to view the board.
emailVisitors must verify their email address via a one-time code.
2faTwo-factor authentication — same shape as email but with stricter verification.

Open to all (default):

{
  "settings": {
    "access": {
      "type": "open_to_all",
      "token_enabled": true
    }
  }
}

Password-protected:

{
  "settings": {
    "access": {
      "type": "password",
      "password": "s3cret",
      "re_authenticate_per_session": true
    }
  }
}

Email verification:

{
  "settings": {
    "access": {
      "type": "email",
      "include_recipients": true,
      "domain_filter_enabled": true,
      "allowed_domains": ["acme.com", "partner.co"],
      "blocked_domain_filter_enabled": false,
      "blocked_domains": [],
      "privacy_message_id": 123,
      "re_authenticate_per_session": false,
      "code_expiration_seconds": 900
    }
  }
}
FieldTypeApplies toDescription
typestringallAccess type: "open_to_all", "password", "email", or "2fa".
token_enabledbooleanopen_to_allEnable token-based access for the board.
passwordstringpasswordThe password visitors must enter.
re_authenticate_per_sessionbooleanpassword, email, 2faIf true, visitors must re-authenticate each browser session.
re_authenticate_after_daysnumberpasswordAlternative to re_authenticate_per_session — re-authenticate after this many days.
include_recipientsbooleanemail, 2faInclude email recipients in board analytics.
domain_filter_enabledbooleanemail, 2faEnable allowed-domain filtering.
allowed_domainsstring[]email, 2faEmail domains permitted to access the board (when domain_filter_enabled is true).
blocked_domain_filter_enabledbooleanemail, 2faEnable blocked-domain filtering.
blocked_domainsstring[]email, 2faEmail domains blocked from accessing the board.
privacy_message_idnumberemail, 2faId of a privacy message to display during email verification.
code_expiration_secondsnumberemail, 2faVerification code expiration time in seconds.
Feature gate. Board access management must be enabled for the organization. If the feature is off, sending settings.access returns 422 with "Board access management is not enabled for this organization".

Example response

{
  "board_id": 12345,
  "board_url": "https://acme.boards.folloze.com/my-vanity-slug",
  "slug": "my-vanity-slug",
  "main_page": {
    "header": { "section_id": "board:default:s_f316586e", "type": "header", "name": "Header", "widgets": { ... } },
    "sections": [
      { "section_id": "board:default:s_abc123", "type": "body", "name": "Single Item", "widgets": { ... } }
    ],
    "footer_id": 789
  },
  "registration_page": {
    "sections": [
      { "section_id": "board:registration:s_reg001", "type": "body", "name": "Registration", "widgets": { ... } }
    ]
  }
}
Registration page in response. The registration_page key appears in the response whenever the template/source board has a registration page — even if you did not send a registration_page in the request. It reflects the registration page as-is from the template.

Errors

StatusWhen
400sections is missing or not an array.
401Authentication is missing or invalid.
403Feature board_creation_api is disabled for the organization.
404template_board_id not found, or footer_id does not exist in the organization.
422registration_page was provided but the template/source board has no registration page.
422slug is already taken: "The vanity URL \"{value}\" is occupied. Try again with a different value".
422settings.access was provided but the board access management feature is not enabled for the organization.
Null values override. Sending null as a leaf value in a widget field clears that field, overriding the template's value. This applies to all widget fields across create and update operations.

Update a board #

PATCH PUT/v1/boards/:board_id

Updates an existing board's sections, widgets, slug, and settings. Accepts the same page structure as POST /v1/boards — main_page, registration_page, or legacy top-level sections. The board must have a published configuration (i.e. it must have been published at least once).

Path parameters

NameTypeDescription
board_idnumberrequiredId of the board to update.

Request body

FieldTypeDescription
main_pageobjectoptionalMain page content overrides. Same shape as on create (page object).
registration_pageobjectoptionalRegistration page content overrides (page object). Only valid if the board has a registration page.
sectionsarrayoptionalLegacy format. Main page section overrides. Equivalent to main_page.sections.
board_namestringoptionalUpdate the board's display name.
headerobjectoptionalLegacy format. Header overrides for the main page.
footer_idnumberoptionalLegacy format. Footer id for the main page.
owner_emailstringoptionalReassign the board to a different owner by email.
tagsstring[]optionalBoard tags to apply.
statestringoptionalBoard state: "online" or "draft". If "online", the board is published after the update.
channelsarrayoptionalChannels to add the board to. See Channels.
slugstringoptionalUpdate the board's vanity URL slug.
settingsobjectoptionalBoard settings. See Access settings.

Example request

PATCH /v1/boards/12345
Content-Type: application/json
Authorization: Basic <your_api_token>

{
  "board_name": "Updated Hub for Acme Corp",
  "slug": "acme-hub-q4",
  "main_page": {
    "sections": [
      {
        "section_id": "board:default:s_abc123",
        "widgets": {
          "banner": {
            "title": "<p>Welcome to Acme, updated!</p>",
            "subtitle": "<p>Your personalized resource hub</p>"
          }
        }
      }
    ]
  },
  "settings": {
    "access": {
      "type": "password",
      "password": "acme2026",
      "re_authenticate_per_session": true
    }
  },
  "state": "online"
}

Example response

{
  "board_id": 12345,
  "board_url": "https://acme.boards.folloze.com/acme-hub-q4",
  "slug": "acme-hub-q4",
  "main_page": {
    "header": { "section_id": "board:default:s_f316586e", "type": "header", "name": "Header", "widgets": { ... } },
    "sections": [
      { "section_id": "board:default:s_abc123", "type": "body", "name": "Banner", "widgets": { ... } }
    ],
    "footer_id": 789
  }
}

Errors

StatusWhen
401Authentication is missing or invalid.
403Feature board_creation_api is disabled for the organization.
404Board not found in the organization.
422Board has no published configuration (must be published at least once before updating).
422slug is already taken.
422settings.access was provided but the access management feature is not enabled.

Publish board #

POST/v1/boards/:id/publish

Publishes a board's unpublished changes, making them live. By default the board is also set to online state; pass go_online=false to publish the configuration without going live.

Path parameters

NameTypeDescription
idnumberrequiredId of the board to publish.

Query / body parameters

NameTypeDescription
go_onlinebooleanoptionalSet the board to online after publishing. Defaults to true. Pass false to publish the configuration without making the board publicly reachable.

Example request

POST /v1/boards/243672/publish
Authorization: Basic <your_api_token>

Example response

{
  "board_id": 243672,
  "board_url": "https://acme.boards.folloze.com/q3-acme",
  "published_at": "2026-05-18T06:12:43Z",
  "online": true
}

Errors

StatusWhen
208No unpublished changes to publish. Response: { "board_id": ..., "message": "No unpublished changes" }.
400Publish failed (e.g. invalid board configuration).
401Authentication is missing or invalid.
403Feature board_creation_api is disabled for the organization.
404Board not found in the organization.

List HTML sections #

GET/v1/boards/:board_id/html_sections

Returns every editable HTML field in a board's unpublished configuration. Use this to discover which fields can be read or updated via the HTML content endpoint.

Path parameters

NameTypeDescription
board_idnumberrequiredId of the board.

Example request

GET /v1/boards/243672/html_sections
Authorization: Basic <your_api_token>

Example response

{
  "html_sections": [
    {
      "section_id": "s_1b61c01c",
      "widget_tag": "html-section",
      "field": "content",
      "url": "/v1/boards/243672/html_sections/s_1b61c01c/html-section/content"
    },
    {
      "section_id": "s_f316586e",
      "widget_tag": "html-header",
      "field": "content",
      "url": "/v1/boards/243672/html_sections/s_f316586e/html-header/content"
    }
  ]
}
These ids are not the canonical board-config ids. The HTML section endpoints use the raw section id (s_1b61c01c), not the board:<page>:<section> form returned by /describe. Use the section_id (or the ready-made url) from this response when calling HTML content — don't carry a canonical id over from /describe.
Empty array is normal. If the board has no unpublished configuration, or no sections use HTML widgets, the response is { "html_sections": [] }.

Response fields

FieldTypeDescription
section_idstringThe section containing this HTML field.
widget_tagstringThe widget type within the section (e.g. html-section, html-header, html-footer).
fieldstringThe field name (typically content).
urlstringConvenience URL for the GET/PUT HTML content endpoint for this field.

Errors

StatusWhen
401Authentication is missing or invalid.
403Feature board_creation_api is disabled for the organization.
404Board not found in the organization.
422Board has no configuration.

Get / update HTML content #

GET PUT/v1/boards/:board_id/html_sections/:section_id/:widget_tag/:field

Read or replace the raw HTML content of a single field in a board's configuration. Both verbs use text/html — not JSON — for the content body.

Path parameters

NameTypeDescription
board_idnumberrequiredId of the board.
section_idstringrequiredSection id (from List HTML sections).
widget_tagstringrequiredWidget type tag (e.g. html-section).
fieldstringrequiredField name (typically content).

GET — read HTML content

Returns the raw HTML of the field. By default reads from the unpublished configuration; pass the ?published query parameter to read the published version instead.

GET /v1/boards/243672/html_sections/s_1b61c01c/html-section/content
Authorization: Basic <your_api_token>

Response: Content-Type: text/html; charset=utf-8

<div class="ql-style-heading4">
  <strong>Your platform isn't truly modern</strong>
</div>

PUT — update HTML content

Replaces the HTML content in the board's unpublished configuration. The request body is raw HTML — not a JSON object.

Content-Type must be text/html. The server returns 415 if you send any other content type. Do not wrap the HTML in a JSON string.
PUT /v1/boards/243672/html_sections/s_1b61c01c/html-section/content
Authorization: Basic <your_api_token>
Content-Type: text/html

<div class="ql-style-heading4">
  <strong>Updated headline copy</strong>
</div>

Response: 200 OK with Content-Type: text/html; charset=utf-8, echoing back the written HTML.

Typical workflow

  1. Create a board via POST /v1/boards (the board is created as a draft with unpublished config).
  2. Call GET /v1/boards/:id/html_sections to discover which HTML fields exist.
  3. Use PUT on each field to inject your custom HTML content.
  4. Call POST /v1/boards/:id/publish to push the changes live.

Errors

StatusWhen
401Authentication is missing or invalid.
403Feature board_creation_api is disabled for the organization.
404Board, section, widget, or field not found.
415Content-Type is not text/html (PUT only).
422Board has no unpublished configuration to update (PUT only).

End-to-end walkthrough #

A common workflow has four steps: pick a template, read its schema, build a payload, create the board.

Step 1 — Pick a template

GET /v1/board_templates
Authorization: Basic <your_api_token>

Returns every template board in your organization with its id, name, and description. Pick the template id you want to base the new board on (e.g. 4321). If you already know the id (e.g. it's stored in your config), you can skip this step.

Step 2 — Read the template's schema

GET /v1/board_templates/4321/describe
Authorization: Basic <your_api_token>

Save the response — it tells you which sections exist, which widgets they contain, what fields each widget exposes, and the header / footer to round-trip back to POST /v1/boards.

Step 3 — Build your payload

Take the response, replace the placeholder text with your real content, drop sections you don't want, and add your header.

{
  "template_board_id": 4321,
  "board_name": "Q3 Campaign — Acme Corp",
  "owner_email": "rep@acme.com",
  "tags": ["acme", "q3-campaign"],
  "state": "online",
  "channels": [
    { "name": "Q3 Campaigns" }
  ],
  "header": {
    "primary_logo": { "url": "https://cdn.example.com/acme.png", "link": "https://acme.com", "alt": "Acme" },
    "tagline": "Built for modern teams",
    "navigation": {
      "items": [
        { "text": "Solutions", "type": "flz-link", "action": { "type": "open_url", "open_url": { "url": "https://acme.com/solutions", "open_in_new_window": true } } },
        { "text": "Pricing",   "type": "flz-link", "action": { "type": "open_url", "open_url": { "url": "https://acme.com/pricing", "open_in_new_window": true } } }
      ]
    },
    "cta": {
      "text": "Talk to sales",
      "type": "flz-primary",
      "action": { "type": "form", "form": { "form_id": "86101" } }
    }
  },
  "footer_id": 789,
  "sections": [
    {
      "section_id": "board:default:s_abc123",
      "name": "Banner - classic",
      "widgets": [
        {
          "widget_id": "w_111",
          "title": "Acme x Folloze",
          "subtitle": "Personalized buyer experiences at scale",
          "cta": {
            "text": "Get started",
            "type": "flz-primary",
            "action": { "type": "open_content_item", "open_content_item": {} }
          },
          "secondary_cta": {
            "text": "Watch demo",
            "type": "flz-secondary",
            "action": { "type": "open_url", "open_url": { "url": "https://acme.com/demo", "open_in_new_window": true } }
          }
        }
      ]
    },
    {
      "section_id": "board:default:s_def456",
      "name": "Columns - Value proposition 4 columns",
      "widgets": [
        {
          "widget_id": "w_222",
          "title": "Why Acme",
          "subtitle": "Three reasons teams choose us",
          "cta": {
            "text": "See all",
            "type": "flz-primary",
            "action": { "type": "open_url", "open_url": { "url": "", "open_in_new_window": true } }
          },
          "repeatable": {
            "columns": [
              { "title": "Faster onboarding",      "paragraph": "Go live in days, not months." },
              { "title": "Personalized at scale",  "paragraph": "1:1 experiences for every account." },
              { "title": "Built-in analytics",     "paragraph": "Know what works and double down." }
            ]
          }
        }
      ]
    }
  ]
}

Step 4 — Create the board

POST /v1/boards
Authorization: Basic <your_api_token>
Content-Type: application/json

<body from step 3>

Response:

{
  "board_id": 99213,
  "board_url": "https://acme.boards.folloze.com/q3-acme",
  "sections": [
    {
      "section_id": "board:default:s_abc123",
      "type": "body",
      "name": "Banner - classic",
      "widgets": [{ "widget_id": "w_888", "title": "Acme x Folloze", "subtitle": "..." }]
    }
  ]
}

You can now share board_url with your audience.


Content API #

The Content API manages the content items in your Folloze organization — the URLs, files, videos, and PDFs that appear on boards. You can ingest assets from any external source (the asset doesn't need to exist in Folloze first), update them, search them, and place them on specific boards with per-board overrides.

What it's for

Endpoints at a glance

EndpointPurpose
POST /v1/itemsCreate a content item from any external source (URL, file, video, PDF).
GET /v1/itemsList & search items in your organization (filter by type, owner, tags, free text).
POST /v1/board_itemsPlace an existing item on a board.
PUT /v1/board_items/:idUpdate a board item's title, description, thumbnail, schedule, status, or categories.

Authentication & base URL #

The Content API is served from the same host as the Board Creation API:

https://content-api.folloze.com

Authentication uses the same scheme as the Board Creation API — an API token in the Authorization header:

Authorization: Basic <your_api_token>

Tokens are issued per organization by Folloze. Contact your Folloze account representative to request a token — the same token grants access to both the Board Creation API and the Content API.

creator_email is required on every write. All POST calls require a creator_email field in the body, and the email must match a real user in your Folloze organization. Unknown emails return {"message":"user not found for creator email"}. Pass the email of a real seller / admin who should be recorded as the creator of the item.

List & search content items #

GET/v1/items

Returns a paginated list of content items in your organization, with filtering by type, owner, tags, and free text.

Query parameters

NameTypeDefaultDescription
pageinteger1Page number for pagination.
per_pageinteger24Items per page (max 100).
sort_bystring—Field to sort results by.
sort_directionstringdescasc or desc.
searchstring—Free-text search across item content.
type[]string—Filter by content type (e.g. video, pdf). Repeatable.
owner[]string—Filter by owner user id. Repeatable.
tags[]string—Filter by tag name. Repeatable.
Use the [] suffix on array params. Without it, only the last value wins. ?type=video&type=pdf filters by pdf only; ?type[]=video&type[]=pdf filters by both.

Examples

# Single filter
curl "https://content-api.folloze.com/v1/items?type[]=video" \
  -H "Authorization: Basic $FOLLOZE_API_TOKEN"

# Combined filters
curl "https://content-api.folloze.com/v1/items?type[]=video&type[]=pdf&owner[]=42&tags[]=webinar&search=demo&page=1&per_page=50&sort_direction=asc" \
  -H "Authorization: Basic $FOLLOZE_API_TOKEN"
const params = new URLSearchParams();
["video", "pdf"].forEach(t => params.append("type[]", t));
params.append("owner[]", "42");
params.append("tags[]", "webinar");
params.set("search", "demo");
params.set("per_page", "50");

const res = await fetch(
  `https://content-api.folloze.com/v1/items?${params}`,
  { headers: { Authorization: `Basic ${FOLLOZE_API_TOKEN}` } }
);
const items = await res.json();
import requests
res = requests.get(
    "https://content-api.folloze.com/v1/items",
    headers={"Authorization": f"Basic {FOLLOZE_API_TOKEN}"},
    params=[
        ("type[]", "video"), ("type[]", "pdf"),
        ("owner[]", "42"),
        ("tags[]", "webinar"),
        ("search", "demo"),
        ("per_page", 50),
    ],
)
items = res.json()

Create a content item #

POST/v1/items

Creates a new content item in your Folloze organization from any external source. The asset itself does not need to exist in Folloze beforehand — provide a public URL (for link items) or a downloadable URL (for file items) and Folloze ingests it. File items are ingested asynchronously — see Async file ingest.

Request body (key fields)

FieldTypeDescription
creator_emailstringrequiredEmail of an existing user in the organization. Recorded as the item's creator.
titlestringrequiredDisplay title of the item.
descriptionstringoptionalLong-form description.
thumbnail_urlstringoptionalCover image URL. If omitted, Folloze will attempt to derive one.
download_urlstringconditionalFor file-type items: the public URL Folloze will pull the asset from.
urlstringconditionalFor URL-type items: the destination URL the item links to.
tagsstring[]optionalTag names to apply. New tags are created on demand.
board_itemobjectoptionalIf present, also places the newly created item on a board in the same call. Saves a round-trip when you're ingesting and placing in one shot. See Categories for the nested categories shape.

Response

Successful create returns 202 Accepted with a tracking payload:

{
  "folloze_id": 770029,
  "provider_asset_id": "abc123",
  "provider_name": "folloze"
}

Attach an item to a board #

POST/v1/board_items

Places an existing content item on a board without creating a duplicate. Per-board presentation fields (title, description, thumbnail, tags, schedule, categories) override the source item's defaults only on this board — the source item is never mutated.

The "personalize at scale" pattern. Keep one canonical content item, then place it on N account-personalized boards with a different per-board title, thumbnail, and tag mix on each. Updating the source item later won't overwrite the per-board overrides you set.
Need to bring in a brand-new asset? Use POST /v1/items first to create the item from its source URL, then call this endpoint with the returned id. Or do both in a single call by including a board_item object on the create request.

Request body

FieldTypeDescription
board_idintegerrequiredTarget board id.
content_center_item_idintegerrequiredId of the existing content item to place on the board (returned from POST /v1/items or GET /v1/items).
creator_emailstringrequiredEmail of an existing user; recorded as the board-item creator.
titlestringoptionalPer-board title override.
descriptionstringoptionalPer-board description override.
thumbnail_urlstringoptionalPer-board thumbnail override.
tagsstring[]optionalPer-board tags.
statusstringoptionalonline or offline.
is_gatedbooleanoptionalWhether viewing the item requires a form fill.
open_in_new_tabbooleanoptionalOpen destination in a new tab.
scheduleobject | falseoptionalVisibility window. Pass false to clear any schedule. Object shape: { start_unix, end_unix, timezone }.
categoriesarrayoptionalCategories to place this item under on the board. See Categories.

Example request

POST https://content-api.folloze.com/v1/board_items
Authorization: Basic <your_api_token>
Content-Type: application/json

{
  "board_id": 241976,
  "content_center_item_id": 509798,
  "creator_email": "admin@follozeqa.com",
  "title": "Shaping Tomorrow's Cities — for JLL",
  "description": "Human-centric development insights tailored for this account.",
  "thumbnail_url": "https://images.folloze.com/image/upload/hsvolbwawc9krdqmybvs.png",
  "tags": ["JLL", "Creating a Space"],
  "status": "online",
  "is_gated": false,
  "open_in_new_tab": true,
  "schedule": {
    "start_unix": 1714500000,
    "end_unix":   1717092000,
    "timezone":   "America/New_York"
  },
  "categories": [
    { "name": "Product Updates" },
    { "name": "Customer Stories" }
  ]
}

Update a board item #

PUT/v1/board_items/:id

Updates the per-board overrides for an existing board item: title, description, thumbnail, schedule, status, and categories. The underlying source content item is never modified.

Path parameters

NameTypeDescription
idintegerrequiredId of the board item (returned when you created it via POST /v1/board_items).

Request body

Same fields as attach to board, except board_id and content_center_item_id are not changeable. Only fields you include are updated — omit a field to leave it as-is.

Example request

PUT https://content-api.folloze.com/v1/board_items/2207832
Authorization: Basic <your_api_token>
Content-Type: application/json

{
  "title": "Test of update",
  "description": "Updated walkthrough of the new platform capabilities.",
  "thumbnail_url": "https://images.folloze.com/image/upload/hsvolbwawc9krdqmybvs.png",
  "status": "online",
  "is_gated": true,
  "open_in_new_tab": true,
  "schedule": {
    "start_unix": 1714500000,
    "end_unix":   1717092000,
    "timezone":   "America/New_York"
  },
  "categories": [
    { "name": "Product Updates" },
    { "name": "Customer Stories" }
  ]
}

Categories & subcategories #

The categories field on board-item endpoints lets you place an item under one or more categories on the board. Each entry can be specified by name (created on demand if missing) or by id (must already exist; otherwise the call fails).

Shape

{
  "categories": [
    {
      "name": "Marketing",
      "sub_categories": [
        { "name": "Email Campaigns" },
        { "id":   456 }
      ]
    },
    { "id": 789 }
  ]
}
FieldTypeDescription
namestringCategory name. Found on the board if it exists; created if it doesn't.
idintegerExisting category id. Errors if not found on this board.
sub_categoriesarraySame name/id rules; nested under the parent category.
Prefer name for cross-board sync. If your CMS has stable category names but the board ids differ across organizations, name-based addressing lets the same payload work everywhere. Use id only when you've cached ids from a previous response.
Legacy field. Earlier integrations used board_item.category_ids (a flat integer array). It still works for backwards compatibility, but new integrations should use categories — it supports name-based lookup and nested subcategories.

Async file ingest #

When you create a file-type item with a download_url, Folloze responds immediately with 202 Accepted and ingests the file asynchronously by pulling it from the URL you provided.

What this means for your client


Folloze 101 — Key concepts

New to Folloze? This is a quick tour of the things the API talks about — boards, sections, content items, and the rest. Anywhere a term is marked with a ? in the API docs above, this is the page that's being linked to.

The big picture

A board is a personalized web page you share with buyers. Boards are built from sections (banner, content block, form, etc.), and each section is populated with content items from your Folloze organization. Marketers usually start by designing a template board in the Folloze UI, then the API takes that template and stamps out personalized copies of it — one per account, campaign, or event.

Boards

TermWhat it means
Board A published web page in Folloze — the thing buyers actually visit. It has a unique board_url (e.g. acme.boards.folloze.com/q3-acme) and is made up of a header, a stack of body sections, and a footer. Help Center →
Template board A board that's been marked as a template inside Folloze. Templates are the starting point for new boards — you take a template, fill in account-specific copy and content, and the API produces a new board based on it. Designers create templates in the Folloze UI. Help Center →
Header The bar at the top of a board: logo(s), navigation links, an optional top-bar CTA, and a tagline. Folloze ships several pre-designed header types (e.g. Header with navigation, Header with 2 logos) — the template's designer picks which one to use. Help Center →
Footer The bottom area of a board (links, legal, social). Footers are reusable across boards in your organization; you reference one by id when creating a board. Help Center →
Board tag A free-form label applied to a board (distinct from content-item tags). Used to organize, filter, and report on boards inside Folloze. Pass tags: ["..."] on POST /v1/boards — new tags are created on demand; existing ones are reused. Help Center →
Channel A grouping of boards used for navigation, sharing, and audience targeting inside Folloze. One board can belong to many channels. On POST /v1/boards reference channels by name — matched exactly (case-sensitive, no trim); created if no match exists. Help Center →
Board state Publish state of a board. "online" means the board is published and reachable at its URL; "draft" means it's saved but not yet published. New boards default to "draft" — pass state: "online" on POST /v1/boards to publish immediately. Help Center →

CTAs & actions

TermWhat it means
CTA (call to action) A clickable button or link on a board section. Every CTA has a label (text), a visual style (type: flz-primary, flz-secondary, or flz-link), and an action that defines what happens on click. Sections can have a primary cta and/or a secondary_cta. CTA configuration reference → Help Center →
CTA action The behaviour triggered when a visitor clicks a CTA. Nine action types are supported: open_url, form, registration, anchor, contact, message, share, send_email_clicked, and open_content_item. Each type carries its own configuration — see the action types table. Help Center →
Privacy message A consent / privacy notice shown alongside contact, message, and share actions. Referenced by privacy_message_id in the action configuration. Privacy messages are managed in the Folloze UI. Help Center →

Sections & ribbons

TermWhat it means
Section A horizontal block of a board — for example, a hero banner, a 4-column value-prop block, a content carousel, or a contact form. The body of a board is just an ordered list of sections. Help Center →
Ribbon (API term) Optional background or decorative layer behind a section — e.g. a coloured shape, a gradient, an image. Ribbons are part of the template's visual design and aren't surfaced as a distinct concept in the Folloze UI; in API calls you usually pass them through unchanged from /describe.
HTML section A section (or header/footer) whose widget exposes raw HTML content via a dedicated html field type. HTML content cannot be set through the normal POST /v1/boards payload — use the HTML content endpoint to read and write it after the board is created, then publish to push changes live.
Image field (API term) An editable image slot on a widget, identified by the image_url schema type. Accepts a public URL, a base64 data URI, or an existing Folloze CDN URL. The API automatically hosts external images on the Folloze CDN during board creation — no separate upload step is needed. On output, the field includes a read-only optimized_url and image_config with the designer's transformation settings. Image configuration reference →

Content items

TermWhat it means
Content item A single asset in your Folloze organization. There are two main types: links (URLs — web pages, articles, third-party content) and files (PDFs, slide decks, videos, images, etc.). Content items live independently of any board; one item can appear on many boards. Help Center →
Content Center The place inside Folloze where all of your organization's content assets are hosted — PDFs, slides, videos, links, images, and so on. It's the central library every board pulls content from, and it's the same library the API exposes via GET /v1/items. Help Center →
Board item A content item that's been placed onto a specific board. You can override the title, description, thumbnail, tags, and visibility schedule per board — without changing the underlying content item. So one canonical case study can appear on twenty different boards with twenty different account-specific titles. Help Center →
Category A grouping bucket for board items on a section — e.g. “Product Updates”, “Customer Stories”. Categories are defined per board and used to organize content into tabs/columns/etc. Help Center →
Subcategory A category nested under another category. Same rules — you can address them by name or by id. Help Center →
Tag A free-form label applied to a content item. Used for searching/filtering items via GET /v1/items?tags[]=…. New tags are created on demand when you reference them. Help Center →

How they fit together

From the API's perspective, generating a personalized board for an account looks like this:

  1. Start with a template board the marketing team designed in the Folloze UI.
  2. Read the template's sections and widgets to see which fields you can fill.
  3. Send a POST /v1/boards with account-specific copy — you get back a new board with its own URL.
  4. Optionally add or update content items in your library, then place them on the new board as board items with per-board titles, thumbnails, and categories.
  5. Share the board URL with your buyer.

Changelog

Audit log of changes to the public APIs. Newest first.

DateAreaChange
2026-10-19 Board Creation API Breaking: repeatable icon renamed to image. Repeatable columns on promo columns, promo carousel and running carousel now take image, and the group setting repeatable.icon.{icon_shape, icon_size} is now repeatable.image.{image_shape, image_size} with the same values. Sending icon fails with 422 unknown field 'icon' (expected one of [title, subtitle, paragraph, image, cta, back, visibility]); /describe returns the new keys. Multi-tab is not renamed: tabs.items[].icon still describes as icon. The known issue now covers image_size: "medium_large" (map to large) alongside "small_medium" (map to medium). Corrected the Q&A path: rows live at repeatable.items[], not repeatable.columns[]. See Sizing repeatable images.
2026-09-24 Board Creation API Review corrections. Validation is documented as per widget, not per field — editing any field of a widget re-validates the whole widget, so a shape change re-checks the stored crop against the new ratio. Round-tripping /describe is restated as a contract Folloze upholds rather than a caveat to design around. Removed internal implementation detail that does not belong in customer docs: the section_id prefix decomposition (it is an opaque string), widget tag normalisation, and an unverified automatic-tint behaviour. See Reading the authoritative schema.
2026-09-22 Board Creation API New endpoint: GET /v1/forms. Lists and searches your organization’s reusable forms, so a form_id for a form or registration CTA can be resolved through the API. Supports query, form_type (classic, external, marketo, eloqua, hubspot), sort_col, sort_direction, and pagination. Forms created inside a single board for its own use are excluded by design.
2026-09-16 Board Creation API Images corrected end to end. image_config is writable, against a per-kind allowlist, and splits into three variants: widget images (shape, crop, fit, flipX, flipY, filter, tint), ribbon backgrounds (same, no fit), and repeatable icons (no crop, shape read-only). maxWidth / maxHeight / artisticFilter / sharpness never existed in this form — sizing is now the read-only cover object, and artisticFilter is filter with 22 named values. crop takes only x / y / width / height and must already match the shape’s ratio. tint.alpha is 0–100. Also documented: what a new url resets, icon_shape / icon_size, image_size, and the four side-by-side side types. See Configuring images.
2026-09-16 Board Creation API Floating sections corrected. The real widget keys are back-to-top, contact-card, and floating-form (previously listed as greeting-popup / floating-video). Entries are keyed by floating_id, carry no ribbon, and floating_sections sits at the top level of /describe, beside theme_id and main_page. See Widget catalog.
2026-09-16 Board Creation API Validation and round-trip guidance. New Reading the authoritative schema section: describe before every write, validation is strict and positive (unknown fields, enums, and types are rejected with a 422 naming the field path), and /describe output is not guaranteed byte-for-byte round-trippable. 422 added to the response-code table.
2026-09-16 Board Creation API Known issue: icon_size: "small_medium". Some boards carry a legacy stored value that /describe emits and the validator correctly rejects (expected one of [small, medium, large]). Map it to small or medium before posting back. Tracked for a data fix. See Sizing icons.
2026-09-04 Board Creation API Clearer failures on create and update. Cases that previously surfaced as a bare 500 — an unresolvable section_id, duplicate anchors, widget field errors, and images Folloze cannot fetch — now return 422 with an errors array naming the offending field path.
2026-08-23 Board Creation API Widget catalog & promoted_form. Added the full list of accepted widget keys (including the -2 variants that share a schema), field references for text-section, image-section, and html-section, and documentation for the promoted_form field. Also documented two silent behaviours: sections using a widget with no API schema describe as "widgets": {}, and html-typed fields are stripped from /describe entirely. See Widget catalog.
2026-08-23 Board Creation API Corrections: CTA action keys and types. The config key for open_content_item is open_content_item, not item_viewer (previously documented incorrectly). form_id, privacy_message_id, email_template_id, and sender.id are numbers, not strings. CTA type accepts nine values, not three, and CTAs also support prefix / suffix. Full form config reference added.
2026-08-20 Board Creation API Canonical section ids. section_id is now a prefixed id of the form board:<page>:<section> (e.g. board:default:s_f316586e, board:registration:s_reg001) in GET /describe, POST /v1/boards, and PATCH /v1/boards/:board_id. Round-trip the string unchanged — bare ids stored from earlier calls no longer resolve. HTML section endpoints are unaffected and keep raw ids. See Section & widget identity.
2026-08-20 Board Creation API theme_id in describe. GET /v1/board_templates/:id/describe and GET /v1/boards/:id/describe now return a top-level theme_id — the board theme applied to the template or board, or null. Read-only. See Describe template / board.
2026-08-18 Board Creation API Breaking: canonical section ids and stricter widget validation. Two server-side changes took effect together. section_id became the prefixed form board:<page>:<section>, so ids captured from an earlier /describe stopped resolving (Section id "s_abc123" not found). At the same time widget validation became strict and positive: unknown fields, out-of-range enums, and wrong types are rejected rather than ignored — which is what surfaced the image_config allowlist to integrations still sending maxWidth / maxHeight. Re-read /describe before every create or update; ids and values from a stored snapshot are not portable across this change. Documented retroactively on 2026-09-16 after customer reports.
2026-08-12 Board Creation API Ribbons are writable. The opaque ribbonParts array is replaced by a structured ribbon object (background, backgroundMobile, optimizedForMobile) that can be updated on its own, without sending widgets. Color backgrounds take a full CSS declaration ("background: #ffffff"); image backgrounds take url + image_config; video backgrounds are read-only. Widget schemas also gained per-element color and visibility maps. See Ribbons.
2026-08-11 Board Creation API CTA visibility documented. The per-widget visibility flags (showButtons, cta, secondary_cta, promoted_form) are now surfaced in the section schema and documented. Writing a CTA does not reveal it — the matching flag must be set to true. See CTA visibility.
2026-07-16 Board Creation API Update board endpoint. New PATCH/PUT /v1/boards/:board_id endpoint to update an existing board's sections, widgets, slug, and settings. Accepts the same page structure as create. See Update board.
2026-07-16 Board Creation API Board settings: slug and access control. POST /v1/boards and PATCH /v1/boards/:board_id now accept a slug field for vanity URLs and a settings.access object for access control (open, password, email, or 2FA). Null leaf values now override template values instead of being rejected. See Access settings.
2026-07-09 Board Creation API Event name and timezone fields. Zoom and Simulive event sections now expose a structured name field and scheduling.timezone (IANA). Scheduling fields renamed from unixStartTime/unixEndTime to startTime/endTime. See Event sections.
2026-07-09 Board Creation API Registration page support. POST /v1/boards now accepts main_page and registration_page objects for multi-page board creation. GET /describe response is now keyed by page (main_page, registration_page). Top-level sections format still supported for backward compatibility. See Create board and Describe template / board.
2026-07-06 Board Creation API Search boards. New GET /v1/boards endpoint to search and list boards by name, status, and owner. See Search boards.
2026-07-06 Board Creation API Describe & create from any board. GET /v1/boards/:id/describe now works on any board (not just templates). POST /v1/boards accepts source_board_id as an alternative to template_board_id. See Describe template / board and Create board.
2026-06-24 Board Creation API Event sections expanded. Added full field reference table, image object schema (url, alt, link), and a dedicated Simulive example. See Event sections (Zoom / Simulive).
2026-06-24 Board Creation API Media library rewritten. Corrected endpoint to GET /v1/organization_media on content-api.folloze.com with Basic auth. Added multi-category filtering (category[]), real response format (media array + pagination object), all 8 categories, and full field reference. See Organization media library.
2026-06-15 Board Creation API CTA open_content_item action. Documented the open_content_item CTA action type with flz_item_id support for both integer and content_center_item_id formats. (The config key was documented as item_viewer until 2026-08-23; the correct key has always been open_content_item.) See CTA: open a content item.
2026-06-15 Board Creation API Organization media library endpoint. Added documentation for GET /v1/organization_media to browse your org’s media library for image URLs. See Organization media library.
2026-06-14 Board Creation API Event sections (Zoom / Simulive). Event sections expose sources.meetingUrl (Zoom) or sources.videoUrl (Simulive), scheduling.unixStartTime/unixEndTime (ISO 8601), view images, and CTA fields. See Event sections (Zoom / Simulive).
2026-06-08 Board Creation API Set content items on sections during board creation. Content sections now accept a sources field on widgets, letting you specify which content item to display via sources.flz_item_id. This bridges the Board Creation API and Content API in a single call — no separate POST /v1/board_items step needed. See Setting items on sections.
2026-05-30 Board Creation API Image transformations via image_config. Added support for optimized_url and writable image_config fields (maxWidth, maxHeight, flipX/Y, tint, crop, shape, artisticFilter, sharpness) on image fields. Enables callers to apply Cloudinary-style transformations through the API.
2026-05-20 Board Creation API Image support across all widget types. Widgets that display images now expose editable image fields in their API schemas. Pass any public URL or base64 data URI and the API auto-uploads to the Folloze CDN. Response includes optimized_url and image_config with the designer's transformation settings. Supported on image sections, single items, video sections, simulive/zoom, carousels, promo columns, multi-tab, Q&A, side-by-side, and headers. See Configuring images.
2026-05-20 Board Creation API Three new endpoints for HTML widget editing and board publishing: Together these enable a create → edit HTML → publish workflow for boards with HTML widget sections.
2026-05-13 Board Creation API CTA configuration across all sections. CTAs now support a full action object with nine action types (open_url, form, registration, anchor, contact, message, share, send_email_clicked, open_content_item) and three button styles (flz-primary, flz-secondary, flz-link). Available on headers, banners, navigation items, and all body sections. See Configuring CTAs.
2026-05-11 Board Creation API Added four optional fields to POST /v1/boards:
  • owner_email — assign a board owner by email at creation. Defaults to the first admin of the organization.
  • tags — apply board tags on creation.
  • state — "online" publishes immediately; "draft" creates as draft (default).
  • channels — attach the new board to one or more channels by id or name.