Documentation Index Fetch the complete documentation index at: https://docs.elasticfunnels.io/llms.txt
Use this file to discover all available pages before exploring further.
All component endpoints authenticate via EF-Access-Key. The brand user’s role must have components module access.
List Components
Return all components for a brand with pagination. This endpoint does not include html, css, or config in the response — use Get Component to retrieve full content for a specific component.
Results per page (1–100, default: 25)
GET /api/brands/{brand}/components
curl "https://app.elasticfunnels.io/api/brands/{brand_id}/components?per_page=100" \
-H "EF-Access-Key: your_api_key_here"
const res = await fetch (
'https://app.elasticfunnels.io/api/brands/{brand_id}/components' ,
{ headers: { 'EF-Access-Key' : 'your_api_key_here' } }
);
const { data , total , current_page , last_page } = await res . json ();
{
"current_page" : 1 ,
"data" : [
{
"id" : 12 ,
"name" : "Hero Section" ,
"code" : "custom-hero-section" ,
"type" : "editor" ,
"screenshot" : null ,
"brand_id" : 42 ,
"created_at" : "2024-11-01T09:00:00.000000Z" ,
"updated_at" : "2024-12-10T14:30:00.000000Z" ,
"tags" : []
},
{
"id" : 13 ,
"name" : "Guarantee Badge" ,
"code" : "custom-guarantee-badge" ,
"type" : null ,
"screenshot" : null ,
"brand_id" : 42 ,
"created_at" : "2024-11-05T10:00:00.000000Z" ,
"updated_at" : "2024-11-05T10:00:00.000000Z" ,
"tags" : []
}
],
"per_page" : 25 ,
"total" : 2 ,
"last_page" : 1 ,
"from" : 1 ,
"to" : 2
}
Get All Components (unpaginated)
Return all components for a brand in a single array, without pagination. Only components that have a builder config are included.
This endpoint returns a lightweight shape: id, name, code, type, screenshot, and (unless ?short=true) config. It does not return html or css. Use Get Component for the full record of a specific component.
Filter by type. Pass editor to return only visual-builder components.
When true, omits config from the response — returns only id, name, code, type, and screenshot. Useful for lightweight dropdowns.
GET /api/brands/{brand}/components/all
curl "https://app.elasticfunnels.io/api/brands/{brand_id}/components/all" \
-H "EF-Access-Key: your_api_key_here"
[
{
"id" : 12 ,
"name" : "Hero Section" ,
"code" : "custom-hero-section" ,
"type" : "editor" ,
"config" : { "data" : { "type" : "wrapper" , "components" : [] }, "styles" : [] },
"screenshot" : null
}
]
Get Component
Retrieve a single component including its full html, css, and config. Accepts either a numeric ID or the component’s code string.
Numeric ID or code string (e.g. custom-hero-section)
GET /api/brands/{brand}/components/{component}
curl https://app.elasticfunnels.io/api/brands/{brand_id}/components/12 \
-H "EF-Access-Key: your_api_key_here"
curl https://app.elasticfunnels.io/api/brands/{brand_id}/components/custom-hero-section \
-H "EF-Access-Key: your_api_key_here"
{
"id" : 12 ,
"name" : "Hero Section" ,
"code" : "custom-hero-section" ,
"type" : "editor" ,
"html" : "<!DOCTYPE html><html><body><section class= \" hero \" >...</section></body></html>" ,
"css" : ".hero { background: #fff; padding: 60px 0; }" ,
"config" : null ,
"brand_id" : 42 ,
"created_at" : "2024-11-01T09:00:00.000000Z" ,
"updated_at" : "2024-12-10T14:30:00.000000Z"
}
Create Component
Create a new reusable component. This is the only endpoint that accepts html, css, and config — Update Component is metadata-only.
Display name shown in the component library
Unique identifier used to embed the component in pages (e.g. custom-guarantee-badge). Auto-generated if omitted. Components are automatically prefixed with custom-.
HTML markup for the component
CSS styles scoped to this component
Builder config object. Required for visual-builder components; omit for plain HTML components.
POST /api/brands/{brand}/components
curl -X POST https://app.elasticfunnels.io/api/brands/{brand_id}/components \
-H "EF-Access-Key: your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"name": "Guarantee Badge",
"code": "custom-guarantee-badge",
"html": "<div class=\"badge\"><img src=\"https://cdn.elasticfunnels.io/42/assets/media/guarantee-badge.png\" alt=\"60-Day Guarantee\"></div>",
"css": ".badge { max-width: 200px; margin: 20px auto; text-align: center; }"
}'
import requests
r = requests.post(
'https://app.elasticfunnels.io/api/brands/ {brand_id} /components' ,
headers = { 'EF-Access-Key' : 'your_api_key_here' , 'Content-Type' : 'application/json' },
json = {
'name' : 'Guarantee Badge' ,
'code' : 'custom-guarantee-badge' ,
'html' : '<div class="badge">...</div>' ,
'css' : '.badge { max-width: 200px; }' ,
},
)
component = r.json()[ 'pageComponent' ]
print (component[ 'id' ], component[ 'code' ])
const res = await fetch (
'https://app.elasticfunnels.io/api/brands/{brand_id}/components' ,
{
method: 'POST' ,
headers: {
'EF-Access-Key' : 'your_api_key_here' ,
'Content-Type' : 'application/json' ,
},
body: JSON . stringify ({
name: 'Guarantee Badge' ,
code: 'custom-guarantee-badge' ,
html: '<div class="badge">...</div>' ,
css: '.badge { max-width: 200px; }' ,
}),
}
);
const { pageComponent } = await res . json ();
200 Created
422 Validation error
{
"pageComponent" : {
"id" : 14 ,
"name" : "Guarantee Badge" ,
"code" : "custom-guarantee-badge" ,
"type" : null ,
"html" : "<div class= \" badge \" >...</div>" ,
"css" : ".badge { max-width: 200px; margin: 20px auto; text-align: center; }" ,
"brand_id" : 42 ,
"created_at" : "2024-12-10T16:00:00.000000Z" ,
"updated_at" : "2024-12-10T16:00:00.000000Z"
}
}
Update Component
Update a component’s metadata only — name and code slug. This endpoint does not accept or persist html, css, or config. To update a component’s HTML content use Update Component Content . To replace a component entirely, delete it and re-create it with Create Component .
html, css, and config are silently ignored on PUT. The endpoint returns 200 even if you include them — but the content is not saved.
Numeric ID or code string
Display name. Required even if you are not changing it — send the current name.
Rename the component’s code slug. If changed, all pages that reference this component are automatically updated to use the new code.
PUT /api/brands/{brand}/components/{component}
curl -X PUT https://app.elasticfunnels.io/api/brands/{brand_id}/components/14 \
-H "EF-Access-Key: your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"name": "Guarantee Badge",
"code": "custom-guarantee-v2"
}'
cURL (rename by current code)
curl -X PUT https://app.elasticfunnels.io/api/brands/{brand_id}/components/custom-guarantee-badge \
-H "EF-Access-Key: your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"name": "Guarantee Badge v2"
}'
200 Updated
422 Missing required name
404 Component not found
{
"id" : 14 ,
"name" : "Guarantee Badge v2" ,
"code" : "custom-guarantee-badge" ,
"type" : null ,
"brand_id" : 42 ,
"updated_at" : "2024-12-11T09:15:00.000000Z"
}
Common causes of errors on PUT: Symptom Cause Fix 422 with name is requiredname is always required by the validator, even if unchangedAlways include "name" in the body 422 code uniqueness errorThe new code is already taken by another component in the same brand Choose a unique code 401 redirect to loginMissing or invalid EF-Access-Key header, or key doesn’t belong to this brand Check header name and key value 403 module accessBrand user role doesn’t have components access Update role permissions in Project Settings html/css/config not updatingThese fields are not writable via PUT Use POST /{component}/editor to update HTML; use DELETE + POST to replace a component entirely
Update Component Content
Update the HTML content of a component. Use this endpoint to publish new HTML or save a draft for later review. This is separate from Update Component , which only handles metadata.
Numeric ID or code string
When true, the HTML is saved as a draft revision and the live component is not updated. Draft revisions are returned by Get Component Content until discarded or published. When omitted (or false), the HTML is published immediately and any existing draft revisions are discarded.
When saving a draft, pass an existing revision_id to update that specific draft instead of creating a new one.
POST /api/brands/{brand}/components/{component}/editor
curl -X POST https://app.elasticfunnels.io/api/brands/{brand_id}/components/custom-hero-section/editor \
-H "EF-Access-Key: your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"html": "<section class=\"hero\"><h1>Updated headline</h1></section>"
}'
curl -X POST "https://app.elasticfunnels.io/api/brands/{brand_id}/components/custom-hero-section/editor?draft=true" \
-H "EF-Access-Key: your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"html": "<section class=\"hero\"><h1>Draft headline</h1></section>"
}'
await fetch (
'https://app.elasticfunnels.io/api/brands/{brand_id}/components/custom-hero-section/editor' ,
{
method: 'POST' ,
headers: {
'EF-Access-Key' : 'your_api_key_here' ,
'Content-Type' : 'application/json' ,
},
body: JSON . stringify ({ html: '<section class="hero"><h1>Updated</h1></section>' }),
}
);
200 Published
200 Saved as draft
{
"success" : true ,
"revision_id" : null
}
Get Component Content
Read the current content of a component. If a draft revision exists, the draft HTML is returned along with its revision_id. Pass ?published=true to always get the live published content, ignoring any draft.
Numeric ID or code string
When true, returns the published (live) HTML even if a draft revision exists.
GET /api/brands/{brand}/components/{component}/editor
curl "https://app.elasticfunnels.io/api/brands/{brand_id}/components/custom-hero-section/editor" \
-H "EF-Access-Key: your_api_key_here"
Response (no draft)
Response (draft exists)
{
"id" : 12 ,
"name" : "Hero Section" ,
"code" : "custom-hero-section" ,
"html" : "<section class= \" hero \" ><h1>Published headline</h1></section>"
}
Discard Draft
Discard all draft revisions for a component, reverting to the live published content.
Numeric ID or code string
POST /api/brands/{brand}/components/{component}/editor/discard
curl -X POST https://app.elasticfunnels.io/api/brands/{brand_id}/components/custom-hero-section/editor/discard \
-H "EF-Access-Key: your_api_key_here"
Get Component Builder Config
Read the full builder config for a component, ready to load into the builder. The server injects brand assets into config.assets, normalizes style selectors, and removes empty ID components before returning.
Numeric ID or code string
Load a specific revision’s config instead of the current published state.
GET /api/brands/{brand}/components/{component}/builder
curl "https://app.elasticfunnels.io/api/brands/{brand_id}/components/12/builder" \
-H "EF-Access-Key: your_api_key_here"
{
"pageComponents" : [
{
"frames" : [
{
"component" : {
"type" : "wrapper" ,
"stylable" : [ "background" , "background-color" , "background-image" ],
"components" : [
{
"type" : "text" ,
"attributes" : { "id" : "c12-hero-heading" },
"components" : [{ "type" : "textnode" , "content" : "Hello World" }]
}
]
}
}
],
"type" : "main"
}
],
"styles" : [
{
"selectors" : [ "#c12-hero-heading" ],
"style" : { "color" : "#333333" , "font-size" : "32px" , "font-weight" : "700" }
},
{
"selectors" : [ "#c12-hero-heading" ],
"mediaText" : "768px" ,
"atRuleType" : "media" ,
"style" : { "font-size" : "22px" }
}
],
"assets" : [
{ "src" : "https://cdn.elasticfunnels.io/42/assets/media/logo.png" , "type" : "image" }
],
"interactions" : [],
"fonts" : []
}
Save Component Builder Config
Save a full builder config — component tree, styles, rendered HTML, CSS, and interactions — for a component. The server processes the payload before storing it and returns the processed result. You must replace your local state with the returned config and HTML before any subsequent save (see Server-side processing below).
Numeric ID or code string
The full builder config as a JSON string (not a nested object — stringify it before sending).
Rendered HTML output from the builder
Rendered CSS output from the builder
Interaction definitions attached to this component
Omit (or any value other than revision) to publish immediately. Pass revision to save as a draft without touching the live component.
When saving a draft (type=revision), pass an existing revision_id to update that draft instead of creating a new one. Revisions older than 1 hour are always replaced with a new draft regardless.
POST /api/brands/{brand}/components/{component}/builder
curl -X POST https://app.elasticfunnels.io/api/brands/{brand_id}/components/12/builder \
-H "EF-Access-Key: your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"config": "{\"pageComponents\":[{\"frames\":[{\"component\":{\"type\":\"wrapper\",\"components\":[{\"type\":\"text\",\"attributes\":{\"id\":\"hero-heading\"},\"components\":[{\"type\":\"textnode\",\"content\":\"Hello World\"}]}]}}],\"type\":\"main\"}],\"styles\":[{\"selectors\":[\"#hero-heading\"],\"style\":{\"color\":\"#333\",\"font-size\":\"32px\"}}],\"interactions\":[],\"fonts\":[]}",
"html": "<div id=\"hero-heading\">Hello World</div>",
"css": "#hero-heading { color: #333; font-size: 32px; }"
}'
curl -X POST https://app.elasticfunnels.io/api/brands/{brand_id}/components/12/builder \
-H "EF-Access-Key: your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"type": "revision",
"config": "...",
"html": "...",
"css": "..."
}'
const config = { /* your builder config object */ };
const res = await fetch (
'https://app.elasticfunnels.io/api/brands/{brand_id}/components/12/builder' ,
{
method: 'POST' ,
headers: {
'EF-Access-Key' : 'your_api_key_here' ,
'Content-Type' : 'application/json' ,
},
body: JSON . stringify ({
config: JSON . stringify ( config ), // must be a string
html: renderedHtml ,
css: renderedCss ,
interactions: [],
}),
}
);
// IMPORTANT: replace your local state with the server-processed result
const { config : processedConfig } = await res . json ();
localConfig = processedConfig ;
200 Published
200 Saved as draft
422 Malformed config
{
"id" : 12 ,
"name" : "Hero Section" ,
"code" : "custom-hero-section" ,
"config" : {
"pageComponents" : [
{
"frames" : [
{
"component" : {
"type" : "wrapper" ,
"components" : [
{
"type" : "text" ,
"attributes" : { "id" : "c12-hero-heading" },
"components" : [{ "type" : "textnode" , "content" : "Hello World" }]
}
]
}
}
],
"type" : "main"
}
],
"styles" : [
{
"selectors" : [ "#c12-hero-heading" ],
"style" : { "color" : "#333" , "font-size" : "32px" }
}
],
"interactions" : [],
"fonts" : []
}
}
Server-side processing
The server modifies the config, HTML, and CSS you send before storing them. The response always contains the processed versions. You must store and re-send the returned values — not your original pre-send state — otherwise the next save will be operating on stale data.
What gets modified:
Field What happens config.styles[*].selectorsAll #id selectors are normalized to #c{componentId}-{baseId}. Any existing c\d+- prefixes are stripped first, then the current component ID prefix is applied. This makes cloned and imported components safe to save without selector collisions. config component tree attributes.idSame scoping applied recursively to every component node’s id attribute. html element IDs and anchor hrefsid="..." and href="#..." values are rewritten to c{componentId}-{baseId} format.css ID selectorsSame prefix normalization. Hex color values (#fff, #333333, etc.) are left untouched. interactions target/trigger selectors#id references in interaction targets and trigger elements are rewritten to match.config.stylesEmpty state fields removed; atRuleType: 'media' is set automatically when mediaText is present.
Corruption guard : If config.data[0] has a single key "0" (a known serialization artifact), the save is aborted with 422 to prevent data loss. This means the config was malformed before being sent — fetch a fresh copy with GET /builder and retry.
Snapshot on publish : Before overwriting the live component, the server creates a revision snapshot of the current published state (at most once per hour). These snapshots are accessible via Get Component Revisions .
Get Component Revisions
List revision history for a component. Revisions are created automatically as hourly snapshots when publishing, and explicitly when saving a draft.
Numeric ID or code string
GET /api/brands/{brand}/components/{component}/revisions
curl "https://app.elasticfunnels.io/api/brands/{brand_id}/components/12/revisions" \
-H "EF-Access-Key: your_api_key_here"
[
{
"id" : 44 ,
"created_at" : "2024-12-11T09:00:00.000000Z" ,
"updated_at" : "2024-12-11T09:00:00.000000Z" ,
"updated_by" : 7 ,
"updated_by_user" : {
"id" : 7 ,
"name" : "Jane Smith" ,
"email" : "jane@example.com"
}
},
{
"id" : 43 ,
"created_at" : "2024-12-10T14:00:00.000000Z" ,
"updated_at" : "2024-12-10T14:00:00.000000Z" ,
"updated_by" : null ,
"updated_by_user" : null
}
]
Delete Component
Delete a component. If the component is still referenced on one or more pages, the request returns a 409 error to prevent accidental data loss. Pass ?force=1 to delete regardless.
Numeric ID or code string
Pass 1 to delete even if the component is still referenced on pages. Pages that reference the component by code will render an empty slot where the component was.
DELETE /api/brands/{brand}/components/{component}
curl -X DELETE https://app.elasticfunnels.io/api/brands/{brand_id}/components/14 \
-H "EF-Access-Key: your_api_key_here"
curl -X DELETE "https://app.elasticfunnels.io/api/brands/{brand_id}/components/14?force=1" \
-H "EF-Access-Key: your_api_key_here"
200 Deleted
409 Component in use
404 Not found
Get Component Usage
Check how many pages reference a component. Optionally return the list of pages for display in tooling.
Numeric ID or code string
Pass pages to include the list of pages that reference this component (capped at 50).
GET /api/brands/{brand}/components/{component}/usage
curl "https://app.elasticfunnels.io/api/brands/{brand_id}/components/custom-guarantee-badge/usage?include=pages" \
-H "EF-Access-Key: your_api_key_here"
{
"component_id" : 14 ,
"component_code" : "custom-guarantee-badge" ,
"usage_count" : 3 ,
"pages" : [
{ "id" : 101 , "slug" : "checkout" , "title" : "Checkout Page" },
{ "id" : 102 , "slug" : "upsell-1" , "title" : "Upsell Step 1" },
{ "id" : 105 , "slug" : "landing-v2" , "title" : "Landing Page v2" }
]
}
Clone Component
Create a copy of a component within the same brand. The clone gets a new auto-generated code and can optionally receive a new name.
Numeric ID or code string of the component to clone
Name for the cloned component. If omitted, the trailing number in the original name is auto-incremented (e.g. Hero 1 → Hero 2).
POST /api/brands/{brand}/components/{component}/clone
curl -X POST https://app.elasticfunnels.io/api/brands/{brand_id}/components/14/clone \
-H "EF-Access-Key: your_api_key_here" \
-H "Content-Type: application/json" \
-d '{ "name": "Guarantee Badge (Copy)" }'
Automated Component Provisioning
Provision a full set of reusable brand components in one run. Because PUT does not update content , the correct pattern for programmatic provisioning is to check whether each component exists first, then create it if it does not — not an upsert.
import requests
BRAND_ID = 42
BASE = f 'https://app.elasticfunnels.io/api/brands/ { BRAND_ID } '
CDN = f 'https://cdn.elasticfunnels.io/ { BRAND_ID } /assets/media'
H = { 'EF-Access-Key' : 'your_api_key_here' , 'Content-Type' : 'application/json' }
def get_existing_codes () -> set :
"""Fetch all component codes already in the brand."""
r = requests.get( f ' { BASE } /components/all?short=true' , headers = H)
r.raise_for_status()
return {c[ 'code' ] for c in r.json()}
def create_component ( code : str , name : str , html : str , css : str = '' ) -> dict :
payload = { 'name' : name, 'code' : code, 'html' : html, 'css' : css}
r = requests.post( f ' { BASE } /components' , headers = H, json = payload)
r.raise_for_status()
comp = r.json()[ 'pageComponent' ]
print ( f ' ✓ created { code } (id= { comp[ "id" ] } )' )
return comp
COMPONENTS = [
{
'code' : 'custom-refund-icon' ,
'name' : 'Refund Icon' ,
'html' : f '<div class="refund"><img src=" { CDN } /refund-icon.png" alt="Money Back Guarantee"></div>' ,
'css' : '.refund { max-width: 80px; margin: 0 auto; }' ,
},
{
'code' : 'custom-guarantee-badge' ,
'name' : '60-Day Guarantee Badge' ,
'html' : f '<div class="guarantee"><img src=" { CDN } /guarantee-badge.png" alt="60-Day Money Back"></div>' ,
'css' : '.guarantee { max-width: 200px; margin: 20px auto; text-align: center; }' ,
},
{
'code' : 'custom-trust-seal' ,
'name' : 'Trust Seal' ,
'html' : f '<div class="trust"><img src=" { CDN } /trust-seal.svg" alt="Secure Checkout"></div>' ,
'css' : '.trust { display: flex; justify-content: center; }' ,
},
{
'code' : 'custom-cc-icons' ,
'name' : 'Credit Card Icons' ,
'html' : f '<div class="cc"><img src=" { CDN } /cc-icons.png" alt="Visa Mastercard Amex"></div>' ,
'css' : '.cc { max-width: 260px; margin: 0 auto; }' ,
},
]
print ( f 'Provisioning components for brand { BRAND_ID } …' )
existing = get_existing_codes()
for comp in COMPONENTS :
if comp[ 'code' ] in existing:
print ( f ' – skipped { comp[ "code" ] } (already exists)' )
else :
create_component( ** comp)
print ( 'Done.' )
const BRAND_ID = 42 ;
const BASE = `https://app.elasticfunnels.io/api/brands/ ${ BRAND_ID } ` ;
const CDN = `https://cdn.elasticfunnels.io/ ${ BRAND_ID } /assets/media` ;
const H = { 'EF-Access-Key' : API_KEY , 'Content-Type' : 'application/json' };
async function getExistingCodes () {
const res = await fetch ( ` ${ BASE } /components/all?short=true` , { headers: H });
if ( ! res . ok ) throw new Error ( `Failed to fetch components: ${ res . status } ` );
const data = await res . json ();
return new Set ( data . map ( c => c . code ));
}
async function createComponent ({ code , name , html , css = '' }) {
const res = await fetch ( ` ${ BASE } /components` , {
method: 'POST' ,
headers: H ,
body: JSON . stringify ({ name , code , html , css }),
});
if ( ! res . ok ) throw new Error ( ` ${ code } : ${ res . status } ${ await res . text () } ` );
const { pageComponent } = await res . json ();
console . log ( ` ✓ created ${ code } (id= ${ pageComponent . id } )` );
return pageComponent ;
}
const components = [
{ code: 'custom-guarantee-badge' , name: '60-Day Guarantee Badge' ,
html: `<div class="guarantee"><img src=" ${ CDN } /guarantee-badge.png" alt="Guarantee"></div>` ,
css: '.guarantee { max-width: 200px; margin: 20px auto; text-align: center; }' },
{ code: 'custom-trust-seal' , name: 'Trust Seal' ,
html: `<div class="trust"><img src=" ${ CDN } /trust-seal.svg" alt="Secure"></div>` ,
css: '.trust { display: flex; justify-content: center; }' },
];
const existing = await getExistingCodes ();
for ( const c of components ) {
if ( existing . has ( c . code )) {
console . log ( ` – skipped ${ c . code } (already exists)` );
} else {
await createComponent ( c );
}
}