Upload a custom HTML design
Fully replace the block builder with your own markup while keeping buttons and attribution working.
Newer surface, staged rollout
The custom-design library, sanitizer, and slot validator described below are implemented and wired end to end — upload, activation, publish, and live serving all work today. The module that lays out its on-disk paths still labels the asset-serving piece a later rollout stage in its own comments, so treat this as a newer, less battle-tested corner of the builder than buttons/profile/gate, and verify a design in preview before publishing it to real traffic.
Instead of the block builder (buttons/profile/gate/etc.), a domain can run your own HTML document as its landing page. TEASE injects one small script to wire up the money buttons and attribution — everything else in your markup, CSS, and animation is served as you wrote it.
Create a design and upload it
Designs are a library, separate from any one domain — create one, upload a document into it, then point a domain at it.
# 1. Create the design
curl -X POST "https://app.tease.link/api/admin/landing-designs" \
-H "Authorization: Bearer $TEASE_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "name": "My hand-built page" }'
# → { "id": 42, "status": "draft", ... }
# 2. Upload a document — a single .html file, or a .zip with index.html + assets
curl -X POST "https://app.tease.link/api/admin/landing-designs/42/upload" \
-H "Authorization: Bearer $TEASE_API_KEY" \
-F "file=@page.zip"A .zip may carry index.html plus CSS/image/font/video assets (.css .png .jpg .jpeg .webp .svg .gif .woff .woff2 .mp4 .webm), up to 50 files and 5 MB uncompressed total; a bare HTML
document is capped at 512 KB. Not sure where to start? GET /api/admin/landing-designs/starter.zip
downloads a starter kit (index.html with every slot, angel.css, a README.md, and a
PROMPT.md written for feeding to an AI page builder).
The two required slots
Your document must carry two data-angel attributes for the upload to be publishable:
| Slot | Purpose |
|---|---|
[data-angel="name"] | Where the creator's name is injected |
[data-angel="buttons"] (a container) or [data-angel="button"] (a row template the runtime clones) | Where the money buttons render — this is the actual payment path |
Missing either one still stores the draft (so you can fix it in the editor) but the upload
response comes back ok: false with the specific slot errors, and such a draft cannot be
activated on a domain until it's fixed.
What gets stripped on upload
Every document is sanitized before it's stored — <script> tags, on* event-handler attributes,
javascript:/vbscript: URIs, <iframe>/<object>/<embed>, <base>, and any navigation
pointing straight at a money destination (only the runtime-wired button slot is allowed to reach
OnlyFans/your smart-link host) are all removed. Inline <style>, animations, gradients,
@font-face, <canvas>, and data: image URIs are left untouched — visual richness survives,
executable code and attribution-bypassing links don't.
Activate it on a domain
curl -X POST "https://app.tease.link/api/admin/bg/config?domain=links.creator.com" \
-H "Authorization: Bearer $TEASE_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "design": "custom", "custom_design_id": 42 }'This re-validates the design's slots and rejects activation (422) if they're missing — a broken design can never go live even if the upload response was already flagged. Once active, publishing the domain (as usual) promotes the design's staged assets to its public, served copies.
Confirm it worked
GET /api/admin/landing-designs/42 echoes back html_draft, warnings, and whether it
has_published. Use GET /api/admin/landing-designs/42/preview to render the draft through the
same runtime the live page uses, before touching any domain. After publishing, load the domain
directly and check the money buttons actually navigate through your tracked link, not a bare
outbound URL.
Deleting a design in use
Deleting a design is refused (409) while any draft or published domain still references it — the error lists which domains, so you know what to switch first.
What's next
- Reference gaps — this feature has no auto-generated API reference page; the route list lives there
- Import an existing bio-link page
- Back to Landing-page builder