API Reference
One API behind the content, the storefront and the customer records — so a publishing pipeline, a catalogue sync and a CRM export all talk to the same place.
Introduction
The ModelPad API is a REST API over JSON. The web application is a client of it, so anything the app can do is available to you: publishing content, managing a product catalogue, reading orders, and syncing contacts into whatever else you run.
All requests go to https://modelpad.app over HTTPS. Request and
response bodies are JSON; there is no form encoding except for file uploads,
which are multipart/form-data.
Resources are addressed by UUID. Creating a record is a PUT to
its id, which means you choose the id client-side and retries are idempotent —
sending the same PUT twice leaves one record, not two.
curl https://modelpad.app/api/notes \ -H "Authorization: Bearer $MODELPAD_TOKEN"
Authentication
Authenticate with an API key sent as a bearer token. Keys are prefixed
mp_ and are shown once, when created — only a hash is stored, so a
lost key is replaced rather than recovered.
Create one from the CLI with modelpad auth login, which walks
you through the browser and stores the key locally, or from
Account → API keys in the app. List and revoke keys at
/api/keys.
Requests without a valid credential get 401. Every request is
scoped to the account that owns the key; there is no cross-account access, and
ids from another account read as though they do not exist.
curl https://modelpad.app/api/contacts \ -H "Authorization: Bearer mp_live_a1b2c3..." # The CLI reads the same credential export MODELPAD_TOKEN=mp_live_a1b2c3... modelpad contacts list
Errors
Conventional HTTP status codes are used. A failure returns a JSON body with
a single error key holding a human-readable message; there is no
machine-readable error code, so branch on the status.
| Status | Meaning |
|---|---|
200 | Success. |
400 | Malformed JSON, or a field failed validation. |
401 | Missing or invalid credential. |
403 | Authenticated, but not permitted — an admin-only operation, or content you have not purchased. |
404 | No such record for this account. |
413 | Upload exceeded the size limit for its media type. |
500 | Something failed our side. |
{
"error": "Video uploads are restricted to admin accounts"
}
Pagination
List endpoints take page (1-based) and limit, and
return a pagination object alongside the records. Limits are
capped per endpoint — asking for more returns the cap rather than an error.
The array key is named for the resource rather than a generic
data: notes come back under notes, contacts under
contacts, media under media_items. Read the key you
asked for.
Prefer has_more over comparing counts yourself; records created
while you page will otherwise shift the arithmetic.
{
"notes": [ { "id": "…", "title": "…" } ],
"pagination": {
"page": 1,
"limit": 50,
"total": 213,
"has_more": true
}
}
Conventions
Creating and updating. Most resources are upserted with
PUT /api/<resource>/:id where you supply the UUID. The write
replaces the whole record, so read it first and merge, or fields you omit are
cleared. The CLI does that merge for you.
Timestamps are RFC 3339 in UTC (created_at,
updated_at).
Money is integer cents on orders and tiers. Product prices are decimal strings.
Note bodies are Lexical editor JSON, not HTML or markdown. Build them by hand only if you must — the CLI converts markdown in both directions, which is usually what you want.
modelpad usage <id-or-slug> first.# Read, merge, write — the safe update pattern
curl https://modelpad.app/api/contacts/$ID \
-H "Authorization: Bearer $MODELPAD_TOKEN" > contact.json
jq '.contact + {company: "Northwind"}' contact.json > updated.json
curl -X PUT https://modelpad.app/api/contacts/$ID \
-H "Authorization: Bearer $MODELPAD_TOKEN" \
-H "Content-Type: application/json" \
--data @updated.json
Command line
Every endpoint is reachable from the modelpad CLI, which
handles authentication, the read-merge-write dance on updates, and markdown
conversion for note bodies. It prints JSON on stdout and human-facing text on
stderr, so it pipes cleanly into jq.
Anything not modelled as a command is still reachable with
modelpad api <METHOD> <path>, using the same
credential.
modelpad auth login modelpad notes create --title "Q3 benchmark" --markdown report.md modelpad notes list --query search=benchmark | jq '.notes[].title' modelpad contacts list --query list=pricing-enquiries # Escape hatch for anything else modelpad api GET /api/dashboard/summary
Editing a site
Everything an editor — a person or an agent — needs to run a site rather than just build it: the site's editorial brief, what it publishes, how it is read, which notes need work, and tests of article titles. A site is named by its id, its slug, or "primary". Start with the overview.
The editor's overview
One read to pick a site up: its brief, its content rule and counts, the ten most recent posts, 30-day traffic, the lint summary, running tests with anything wrong with them, the Search Console connection, and an attention list of what to deal with first, each with the command that looks into it.
modelpad sites overview ember-and-salt
{
"site": { "id": "82b4…", "name": "Ember & Salt", "url": "https://modelpad.app/acme/ember-and-salt" },
"brief": { "fields": { "niche": "…" }, "missing": ["cadence"], "markdown": "# Editorial brief: …" },
"content": { "rule": [{ "facet": "Category", "name": "Recipes" }], "published_notes": 42, "recent": [ … ] },
"traffic": { "totals": { "views": 1830, "visitors": 1204, "read_rate": 0.41 }, "top_content": [ … ] },
"lint": { "scanned": 61, "unedited_published": 3, "summary": { "no_meta_description": 12 } },
"experiments": [],
"attention": ["3 published note(s) read like unedited AI chat transcripts: modelpad notes lint …"]
}
Read the editorial brief
The brief as named fields (niche, audience, voice, topics, publish_criteria, style_rules, cadence, goals, notes), as one markdown document, and the fields still empty.
modelpad sites brief primary
Write the editorial brief
Sets the fields sent and keeps the rest. An empty string clears a field; an unknown field is refused by name.
| Parameter | Description |
|---|---|
|
niche, audience, voice, …
string
optional
|
Any of the brief's fields. |
modelpad sites set-brief primary --set voice="Plain, first person, no hype"
What a site publishes
A published note has exactly one home site: the site it is pinned to, else the first site whose content rule it matches, else the primary site. Its page is served there, its sitemap entry and canonical URL are there, and a request for it on another site of the account redirects. Returns the site's rule and the notes that live on it, each with its URL and why (pinned, rule or default). Set the rule with PUT /api/sites/:id {"content_facets": [...]} — taxonomy value ids or "Facet=Value" names.
| Parameter | Description |
|---|---|
|
public
boolean
optional
|
false includes unpublished notes. |
modelpad sites content ember-and-salt modelpad sites update ember-and-salt --set 'content_facets=["Category=Recipes"]'
Pin a note to a site
The exception to the rules: this note lives on this site. {"site": ""} removes the pin. GET the same path to see where a note lives and why.
| Parameter | Description |
|---|---|
|
site
string
Required
|
Site id or slug; empty to unpin. |
modelpad notes set-site <note id> --set site=ember-and-salt
Site traffic
Views, unique visitors, average engaged seconds, read rate and click-through rate for the period, the previous period for comparison, a daily series, top content, referrers, campaigns (utm_*) and devices. Views are reported by a beacon from the reader's browser: crawlers, link previews and the owner's own signed-in visits are not counted, and no cookies are set — a visitor is a hash that rotates daily. The site may be "all".
| Parameter | Description |
|---|---|
|
days
integer
optional
|
Length of the period. Defaults to 30; at most 730. |
modelpad sites stats primary --query days=90
Every article with its numbers
All public notes that live on the site with views, visitors, read rate and click-through for the period — including the ones nobody read, which a top-content list cannot show.
| Parameter | Description |
|---|---|
|
days
integer
optional
|
Defaults to 30. |
|
sort
string
optional
|
views (default), least, visitors, read_rate, click_through_rate, avg_seconds, published. |
modelpad sites content-stats primary --query sort=least
One article's readership
Totals and trend for one note, its referrers, the pages of the site that sent readers to it, and where they went next.
| Parameter | Description |
|---|---|
|
days
integer
optional
|
Defaults to 90. |
modelpad notes stats <note id>
Notes that need an editor
Every note with something wrong, worst first, and a count per check. Checks come in three groups: draft (the note reads like an unedited AI chat: a prompt for an opening, assistant boilerplate, speaker labels), content (a heading that repeats the title, numbered steps that all read "1.", unconverted markdown, encoding damage, thin or empty bodies) and publish (a published note without a meta description, cover image, slug or taxonomy value). unedited is true when a note has two or more draft findings.
| Parameter | Description |
|---|---|
|
site
string
optional
|
Only notes that live on this site (id or slug). |
|
shared
boolean
optional
|
Only published (true) or unpublished (false) notes. |
|
unedited
boolean
optional
|
Only notes that read like unedited transcripts. |
|
group
string
optional
|
draft, content or publish. |
|
code
string
optional
|
One check, e.g. mojibake. |
|
checks
boolean
optional
|
true returns just the list of checks. |
modelpad notes lint --query site=primary --query shared=true --query unedited=true
Repair encoding damage
Fixes text that was decoded with the wrong code page on import ("SautΘ" → "Sauté", "jalape±o" → "jalapeño") in titles and bodies. Only characters inside a word are touched, so "5 ± 2" and "Θ(n)" are left alone. A dry run unless dry_run is false; each repaired note keeps its previous text as a revision. Words whose character was lost outright are listed as unrecoverable.
| Parameter | Description |
|---|---|
|
dry_run
boolean
optional
|
Defaults to true: report what would change. |
|
ids
array
optional
|
Limit the repair to these note ids. |
modelpad notes fix-encoding modelpad notes fix-encoding --set dry_run=false
Test an article's title
An experiment with target_type "note" and target_id tests that article's title and description instead of a page block. Each variant carries overrides ({"title", "description"}); the control carries none. A visitor is assigned one variant and sees its wording wherever the article is named. goal says what counts: click (shown in a list → article opened), read (article opened → read to the end), lead or purchase. Crawlers are never assigned, so search engines always see the real title. The CLI command does the experiment, both variants and the start in one step.
| Parameter | Description |
|---|---|
|
goal
string
optional
|
"" (signup or purchase), lead, purchase, read or click. |
|
target_type
string
optional
|
"note" for an article test; empty for a page-block test. |
|
target_id
uuid
optional
|
The note under test. |
modelpad notes test-title <note id> --set title="A sharper headline" --set goal=click modelpad experiments stats <experiment id> modelpad experiments end <experiment id>
Check a theme with code
A theme can re-implement blocks (blocks: {"<slug>": {"template", "css", "js"}} — same props, its own markup, styles and script), add markup to <head> and run a site script. This validates an unsaved theme and answers with errors and warnings; POST/PUT /api/themes take the same fields. GET /api/themes/reference documents the package layout, the template helpers and what props carry.
modelpad themes check my-theme modelpad themes push my-theme
Redirect old addresses
Replaces the site's redirects. POST adds or changes one {from, to}; DELETE ?from= removes one; GET lists them. A from path ending in /* matches everything beneath it and * in to receives the rest. Redirects answer only requests nothing else does, with a 301.
| Parameter | Description |
|---|---|
|
redirects
array
Required
|
[{"from": "/blog/*", "to": "/doc/*"}] |
modelpad sites add-redirect <site> --set from=/blog/* --set to=/doc/*
Site icon and share image
favicon_media_id and share_image_media_id name images in the media library (an id, or the exact media name; "" removes). The icon links in every page's head are generated from the favicon. A page's og:image is, in order: the one set on the page or the article's featured image, the first picture the page shows (the first image in an article's body), then the site's share image. Pages also carry og:site_name, twitter:card/title/description/image and image dimensions.
modelpad sites update <site> --set favicon_media_id=<media id> --set share_image_media_id=<media id>
Connect Search Console
Connects a site to its Google Search Console property with a service account: create one in Google Cloud, add its email as a user of the property in Search Console, and send its JSON key here. The connection is tested before it is saved; the key is stored encrypted and never returned. GET the same path for the status and setup steps; DELETE removes it.
| Parameter | Description |
|---|---|
|
property
string
Required
|
sc-domain:example.com for a domain property, or https://example.com/ for a URL-prefix one. |
|
credentials
object
Required
|
The service-account key file's JSON. |
modelpad sites search-connect primary --file service-account.json --set property=sc-domain:example.com
Search performance
Clicks, impressions, click-through rate and average position from Search Console, by query, page, date, country or device. GET /api/notes/:id/search is the same report for one article's URL.
| Parameter | Description |
|---|---|
|
by
string
optional
|
query (default), page, date, country, device — comma-separated to combine. |
|
days
integer
optional
|
Defaults to 28. Data lags about two days. |
|
page
string
optional
|
Only URLs containing this. |
|
query
string
optional
|
Only queries containing this. |
|
limit
integer
optional
|
Rows. Defaults to 50. |
modelpad sites search primary --query by=query --query days=28
Notes
Articles and documents. Notes are the unit of long-form content: they can be nested into a tree, published to a public URL, and gated behind an access rule.
List notes
Returns notes owned by the account, most recently edited first. Each note carries published_at (when it first went public), home_site_id and url (where it lives publicly).
| Parameter | Description |
|---|---|
|
search
string
optional
|
Full-text match against title and body. |
|
parent
uuid
optional
|
Only direct children of this note. |
|
site
string
optional
|
Only notes that live on this site (id or slug). |
|
shared
boolean
optional
|
Only published (true) or unpublished (false) notes. |
|
sort
string
optional
|
published, created, updated (default) or title; order=asc reverses. |
|
body
boolean
optional
|
false leaves the documents out — for scanning many notes. |
|
page
integer
optional
|
1-based page number. Defaults to 1. |
|
limit
integer
optional
|
Records per page. Defaults to 50. |
curl https://modelpad.app/api/notes?limit=2 \ -H "Authorization: Bearer $MODELPAD_TOKEN"
modelpad notes list --query limit=2
{
"notes": [
{
"id": "8f14e45f-…",
"title": "Procurement checklist",
"slug": "procurement-checklist",
"is_shared": true,
"parent": null,
"tags": ["guides"],
"updated_at": "2026-08-02T09:14:00Z"
}
],
"pagination": { "page": 1, "limit": 2, "total": 37, "has_more": true }
}
Retrieve a note
Returns one note including its full body as Lexical JSON. Add ?format=markdown to also get body_markdown, the body rendered as markdown.
| Parameter | Description |
|---|---|
|
format
string
optional
|
markdown — include body_markdown alongside the Lexical body. |
curl https://modelpad.app/api/notes/8f14e45f-… \ -H "Authorization: Bearer $MODELPAD_TOKEN"
modelpad notes get 8f14e45f-… modelpad notes get 8f14e45f-… --as-markdown
Create or update a note
Upserts the note at that id. The write replaces the record, so send the whole thing. Author in markdown with body_markdown — the server converts it to the editor's document format (headings, lists, links, emphasis, code blocks, tables) exactly as the app would.
| Parameter | Description |
|---|---|
|
title
string
Required
|
Display title. |
|
body
string
optional
|
Lexical document JSON, as a string. Omit when sending body_markdown. |
|
body_markdown
string
optional
|
The body as markdown; converted server-side. Ignored when body is also sent. |
|
slug
string
optional
|
URL segment for the public page. Derived from the title when omitted. |
|
meta_description
string
optional
|
Search snippet; see /seo. Omitted fields keep their stored value. |
|
og_title
string
optional
|
Link-preview title override; see /seo. |
|
og_description
string
optional
|
Link-preview description override; see /seo. |
|
parent
uuid
optional
|
Parent note, for nesting. |
|
tags
array
optional
|
Slash-separated tag paths, e.g. guides/procurement. |
curl -X PUT https://modelpad.app/api/notes/8f14e45f-… \
-H "Authorization: Bearer $MODELPAD_TOKEN" \
-H "Content-Type: application/json" \
-d '{"title":"Procurement checklist","body_markdown":"# Checklist\n\n- Get three quotes\n- Check the **renewal** terms"}'
modelpad notes create --title "Procurement checklist" --markdown checklist.md
Read search and share metadata
What the public page's <head> is built from: the stored slug, meta_description, og_title and og_description; the cover image used for og:image; the canonical url; `effective`, the values emitted once fallbacks apply (meta description → first body text, Open Graph copy → title and description); and `warnings` for anything a crawler or share card would clip or miss.
curl https://modelpad.app/api/notes/8f14e45f-…/seo -H "Authorization: Bearer $MODELPAD_TOKEN"
modelpad notes seo 8f14e45f-…
{
"seo": {
"note_id": "8f14e45f-…",
"is_shared": true,
"url": "https://modelpad.app/acme/doc/procurement-checklist",
"title": "Procurement checklist",
"slug": "procurement-checklist",
"meta_description": "",
"og_title": "",
"og_description": "",
"og_image_url": "https://cdn.modelpad.app/media/…/cover_large.jpg",
"effective": {
"title": "Procurement checklist",
"description": "Get three quotes. Check the renewal terms…",
"og_title": "Procurement checklist",
"og_description": "Get three quotes. Check the renewal terms…"
},
"warnings": ["no meta_description: the first body text is used as the search snippet"]
}
}
Set search and share metadata
Partial update: only the fields sent change. An empty string clears a field back to its fallback (for slug, back to serving by id). Answers with the same shape as GET, warnings included, so one call writes and verifies.
| Parameter | Description |
|---|---|
|
slug
string
optional
|
URL segment: lowercase letters, digits and hyphens, unique per account. 409 if another note has it. |
|
meta_description
string
optional
|
Search-result snippet. Aim for 120–160 characters; longer is kept but warned about. |
|
og_title
string
optional
|
Link-preview title. Falls back to the title; under 60 characters. |
|
og_description
string
optional
|
Link-preview description. Falls back to the meta description; under 200 characters. |
curl -X PATCH https://modelpad.app/api/notes/8f14e45f-…/seo -H "Authorization: Bearer $MODELPAD_TOKEN" -H "Content-Type: application/json" -d '{"slug":"procurement-checklist","meta_description":"A four-step checklist for buying software without getting burned on renewals."}'
modelpad notes set-seo 8f14e45f-… --set slug=procurement-checklist --set meta_description="A four-step checklist…"
List child notes
Direct children only. Use /descendants for the whole subtree.
modelpad notes children 8f14e45f-…
Move a note
Reparents the note. Pass a null parent to move it to the top level.
modelpad notes move 8f14e45f-… --set parent=<uuid>
Delete a note
Permanent. Check what references it first.
modelpad usage 8f14e45f-… && modelpad notes delete 8f14e45f-…
Pages
Landing pages, the account home page, and the template pages that render every note or product. A page is an ordered array of blocks, each naming a block type and its props.
List pages
All landing pages except the home page, which has its own endpoint.
modelpad pages list
{
"landing_pages": [
{
"id": "3c6e0b8a-…",
"title": "Enterprise plan",
"slug": "enterprise",
"published": true,
"meta_description": "Volume pricing and onboarding."
}
],
"pagination": { "page": 1, "limit": 50, "total": 6, "has_more": false }
}
Retrieve the home page
The page served at the account root. Provisioned on first access, so this never 404s.
modelpad pages home
Create or update a page
Upserts the page. Blocks are replaced wholesale — read, modify, write back.
| Parameter | Description |
|---|---|
|
title
string
Required
|
Page title, used in the <title> tag. |
|
slug
string
optional
|
URL segment under /pages/. |
|
blocks
array
optional
|
Ordered blocks. Each has instance_id, section_type and props. |
|
meta_description
string
optional
|
Meta description for search results. |
|
published
boolean
optional
|
Whether the public URL serves it. |
curl -X PUT https://modelpad.app/api/landing-pages/3c6e0b8a-… \
-H "Authorization: Bearer $MODELPAD_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"title": "Enterprise plan",
"slug": "enterprise",
"published": true,
"blocks": [
{ "instance_id": "hero-1", "section_type": "hero",
"props": { "heading": "Built for volume" } }
]
}'
modelpad pages update 3c6e0b8a-… --set 'blocks=[{"section_type":"hero","props":{}}]'
List a page's blocks
The blocks in order — position, instance_id, type, a one-line summary, style — with anything that looks wrong: an unknown block type, props the block does not have.
modelpad pages blocks <page>
Add a block
Adds one block, starting from its type's default props with yours merged on top. The page row is locked for the change, so concurrent writers cannot erase each other. Returns the page's blocks afterwards.
| Parameter | Description |
|---|---|
|
section_type
string
Required
|
Block type slug from GET /api/blocks. |
|
props
object
optional
|
Props to set over the defaults. |
|
style
object
optional
|
scheme (light|dark), accent, background — how this block departs from the site theme. |
|
at
integer
optional
|
1-based position; omitted, the block goes last. |
modelpad pages add-block <page> hero --set heading="Hello"
Change a block
:block is a position, an instance_id, or a type the page has exactly one of. props and style merge; a null prop or an empty style value removes it. template_override sets the block's own template (empty string resets it).
modelpad pages set-block <page> hero --set subtitle="New" --set style.scheme=dark
Move a block
Body {at} is the 1-based position to move it to.
modelpad pages move-block <page> <block> --at 1
Remove a block
A block that is part of an A/B test is refused unless ?force=true.
modelpad pages remove-block <page> <block>
A site's template pages
kind is header or footer (the parts every page of the site shares) or note or product (the layout every article or product page renders through). Provisioned on first use; ?site_id= picks a site other than the primary. Edit them like any page.
modelpad pages blocks header
Render blocks without saving
Renders an arbitrary block array. Returns html (a body fragment), css (content-widget styles plus the theme) and head (the theme's font links) — the pieces of a page, not a document. The theme is the one named by theme_id, else that of site_id, else the primary site's; theme {tokens, custom_css} previews an unsaved theme.
modelpad api POST /api/landing-pages/preview --data '{"blocks":[]}'
Make a page the home page
Promotes this page to the account root. The previous home page becomes an ordinary page.
modelpad pages set-home 3c6e0b8a-…
Block types
The building blocks pages are made of. Each block type carries a prop schema, which drives the editor's form, and a Go template that renders it. System types are read-only; duplicate one to get an editable copy.
List block types
System types plus any this account owns, templates included.
modelpad blocks list
The block catalog
Every block type without its template: slug, name, category, description and prop schema. ?slug= returns one block with its default props. System blocks are defined by files shipped with the server.
modelpad blocks catalog
Create or update a block type
Defines a reusable block. The template is Go html/template with the block's props in scope.
| Parameter | Description |
|---|---|
|
slug
string
Required
|
Stable identifier referenced by page blocks. |
|
name
string
Required
|
Display name in the block picker. |
|
category
string
Required
|
Grouping in the picker. |
|
prop_schema
array
Required
|
Prop definitions: key, label, type. |
|
go_template
string
Required
|
Markup for the block. |
modelpad blocks update <id> --file block.json
Entity fields available to templates
What a block template can read off a linked record — useful when authoring prop schemas that point at products, notes or media.
modelpad blocks fields
Sites & themes
An account runs one or more sites; each site has a home page, pages, article and product templates, and one theme. Block templates are semantic HTML with mp-* classes and no styling of their own — the site's theme supplies every colour, font and spacing as design tokens (CSS custom properties) plus optional custom CSS. Built-in themes have ids like builtin:signature and are read-only; copy one to change it.
List sites
Every site with its home page id, page and domain counts, and its theme ("" means the default built-in).
modelpad sites list
Update a site
A patch: only the fields sent change. Setting theme restyles every page, article and product page on the site.
| Parameter | Description |
|---|---|
|
name
string
optional
|
Display name. |
|
slug
string
optional
|
Path under the account for non-primary sites. |
|
theme
string
optional
|
A theme id: builtin:<slug>, one of the account's themes, or "" for the default. |
modelpad sites update <site id> --set theme=builtin:editorial
List themes
Built-in themes, then the account's own, each with its full token set. Also returns token_spec and used_by (theme id → site names).
modelpad themes list
What a theme can style
The token spec (key, type, allowed values, default), the CSS variables tokens become, the class names the built-in block templates use, and the per-block override props. Read this before writing a theme's custom CSS or a custom block template.
modelpad themes reference
Create a theme
Starts from the defaults, or from another theme with copy_from.
| Parameter | Description |
|---|---|
|
name
string
optional
|
Display name. |
|
copy_from
string
optional
|
Theme id to copy tokens and CSS from. |
|
tokens
object
optional
|
Token values keyed by token name. |
|
custom_css
string
optional
|
CSS appended after the theme's own styles. |
modelpad themes create --name "Blog" --set copy_from=builtin:turing-registry
Update a theme
A patch. tokens merges onto the stored set, so one token can change on its own; a token or value the spec does not allow is a 400. Built-in themes cannot be updated.
| Parameter | Description |
|---|---|
|
name
string
optional
|
Display name. |
|
tokens
object
optional
|
Tokens to change. |
|
custom_css
string
optional
|
Replaces the theme's custom CSS. |
modelpad themes update <id> --set tokens.accent=#0af --css theme.css
A theme's stylesheet
The complete CSS a page under this theme carries: tokens, block styles, then the theme's custom CSS. Returned as text/css.
modelpad themes css builtin:signature
Delete a theme
Sites using it return to the default theme.
modelpad themes delete <id>
Media
Images, video and files. Uploading is two steps: create the record, then post the bytes to it. Images get responsive variants generated automatically.
List media
Note the array key is media_items.
| Parameter | Description |
|---|---|
|
search
string
optional
|
Match against name and description. |
|
page
integer
optional
|
1-based page number. |
|
limit
integer
optional
|
Records per page, max 200. |
modelpad media list
{
"media_items": [
{
"id": "b1946ac9-…",
"name": "warehouse.jpg",
"file_url": "https://cdn.modelpad.app/media/…/warehouse.jpg",
"file_type": "image/jpeg",
"file_size": 284119,
"thumbnails": {
"thumb": { "url": "…", "width": 150, "height": 100 },
"medium": { "url": "…", "width": 800, "height": 533 }
}
}
],
"pagination": { "page": 1, "limit": 50, "total": 12, "has_more": false }
}
Create a media record
Step one of an upload: reserve the id and set the name. The record exists with no file until you upload one.
modelpad media create --set id=<uuid> --set name=warehouse.jpg
Upload the file
Step two: multipart/form-data with the bytes in a field named file. Images get thumbnail variants generated synchronously; other types are stored as-is.
| Parameter | Description |
|---|---|
|
file
file
Required
|
The file, as multipart/form-data. |
curl -X POST https://modelpad.app/api/media/b1946ac9-…/upload \ -H "Authorization: Bearer $MODELPAD_TOKEN" \ -F "[email protected]"
{
"media": {
"id": "b1946ac9-…",
"file_url": "https://cdn.modelpad.app/media/…/warehouse.jpg",
"file_type": "image/jpeg",
"file_size": 284119,
"thumbnails": { "thumb": { "url": "…", "width": 150, "height": 100 } }
}
}
Regenerate thumbnails
Re-derives image variants from the stored original. Rejects non-images with 400.
modelpad media regenerate-thumbnails b1946ac9-…
Products
Everything you sell: physical goods, digital downloads, and access passes. Products carry taxonomy values so catalogues can be filtered.
List products
modelpad products list
{
"products": [
{
"id": "d3d94468-…",
"name": "Compliance Toolkit",
"slug": "compliance-toolkit",
"price": 149,
"product_type": "digital",
"published": true
}
],
"pagination": { "page": 1, "limit": 50, "total": 9, "has_more": false }
}
Create or update a product
| Parameter | Description |
|---|---|
|
name
string
Required
|
Product name. |
|
price
number
Required
|
Price as a number, e.g. 149.00. |
|
description
string
optional
|
Shown on the product page and in meta tags. |
|
product_type
string
optional
|
digital or physical. |
|
slug
string
optional
|
URL segment. |
|
published
boolean
optional
|
Whether the public page serves it. |
modelpad products create --name "Compliance Toolkit" --set price=149.00
List attached files
The media records a buyer receives after purchase.
modelpad products downloads d3d94468-…
Attach a file
Links a media record to the product as a deliverable. Buyers get signed, expiring links.
| Parameter | Description |
|---|---|
|
media_id
uuid
Required
|
The media record to deliver. |
modelpad products add-download d3d94468-… --set media_id=<uuid>
Set taxonomy values
Assigns facet values so the product appears under the right catalogue filters.
modelpad products set-taxonomy d3d94468-… --file values.json
Contacts
People. Created by hand, by a lead form on a page, or through the API. Each contact accumulates the submissions, orders and notes attached to them.
List contacts
| Parameter | Description |
|---|---|
|
search
string
optional
|
Match against name, email and company. |
|
list
string
optional
|
Only contacts on this list, by slug. |
|
sort
string
optional
|
activity (default: most recent note, form submission, message, meeting, order or edit — returned as last_activity / last_activity_kind), name (by last name — the final word of the name — then full name), or created. |
|
page
integer
optional
|
1-based page number. |
|
limit
integer
optional
|
Records per page. |
curl "https://modelpad.app/api/contacts?list=pricing-enquiries" \ -H "Authorization: Bearer $MODELPAD_TOKEN"
modelpad contacts list --query list=pricing-enquiries
{
"contacts": [
{
"id": "c9f0f895-…",
"name": "Dana Whitfield",
"email": "[email protected]",
"company": "Northwind Logistics",
"title": "Head of Operations",
"created_at": "2026-07-30T11:02:14Z"
}
],
"pagination": { "page": 1, "limit": 50, "total": 148, "has_more": true }
}
Create or update a contact
| Parameter | Description |
|---|---|
|
name
string
Required
|
Full name. |
|
email
string
optional
|
Email address. |
|
phone
string
optional
|
Phone number. |
|
company
string
optional
|
Organisation. |
|
title
string
optional
|
Job title. |
|
website
string
optional
|
URL. |
|
notes
string
optional
|
Freeform internal notes. |
curl -X PUT https://modelpad.app/api/contacts/c9f0f895-… \
-H "Authorization: Bearer $MODELPAD_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"Dana Whitfield","email":"[email protected]"}'
modelpad contacts create --name "Dana Whitfield" --set [email protected]
List interactions
The contact's activity log, newest first: contact_created, note_added, form_submitted, message_sent, message_received, event_scheduled (dated when the meeting was set up; start_time in metadata), order_placed, list_joined. Each row says who acted (user or contact) and what it is about (subject_type, subject_id). The newest entry is also projected onto the contact as last_activity / last_activity_kind.
| Parameter | Description |
|---|---|
|
contact_id
uuid
optional
|
Restrict to one contact. |
modelpad contacts activities c9f0f895-…
List transactions
What this contact has paid you — useful for building an account view.
modelpad api GET /api/contact-transactions --query contact_id=c9f0f895-…
Add an internal note
Freeform note attached to a contact. Never shown publicly.
modelpad api POST /api/contact-notes --set contact_id=<uuid> --set body="Asked about volume pricing"
Contact lists
Segments. A lead form block names a list by slug, and every submission through that form joins it — which is how a page becomes a source of qualified contacts.
List contact lists
Includes a member count per list.
modelpad lists list
{
"contact_lists": [
{
"id": "6c8349cc-…",
"name": "Pricing enquiries",
"slug": "pricing-enquiries",
"member_count": 148
}
]
}
Create or update a list
| Parameter | Description |
|---|---|
|
name
string
Required
|
Display name. The slug derives from it when not given. |
|
slug
string
optional
|
What a lead form block references. |
modelpad lists create --name "Pricing enquiries"
List members
modelpad lists members pricing-enquiries
Add a contact to a list
| Parameter | Description |
|---|---|
|
contact_id
uuid
Required
|
The contact to add. |
modelpad lists add pricing-enquiries <uuid>
Remove a contact from a list
Removes the membership. The contact itself is untouched.
Add many contacts at once
One request for a whole selection — every contact, everyone at a company, or a copy of another list. The list may be given by id, slug or name. Adding is idempotent: contacts already on the list are left as they are.
| Parameter | Description |
|---|---|
|
contact_ids
uuid[]
optional
|
Specific contacts to add. |
|
all
boolean
optional
|
Select every contact in the account (narrowed by the filters below). Required unless contact_ids is given. |
|
search
string
optional
|
With all: only contacts whose name, email or company contains this. |
|
company
string
optional
|
With all: only contacts at this company. |
|
from_list
string
optional
|
With all: only contacts already on this other list (id or slug). |
|
source
string
optional
|
Recorded on each new membership, for auditing. |
curl -X POST https://modelpad.app/api/contact-lists/pricing-enquiries/members/bulk \
-H "Authorization: Bearer $MODELPAD_TOKEN" \
-H "Content-Type: application/json" \
-d '{"all": true, "company": "Northwind"}'
modelpad lists add pricing-enquiries --all --company Northwind
{
"contact_list": { "id": "6c8349cc-…", "name": "Pricing enquiries", "slug": "pricing-enquiries", "member_count": 160 },
"added": 12,
"matched": 14,
"member_count": 160
}
Remove many contacts at once
The bulk counterpart of the DELETE above, taking the same selection as the bulk add: contact_ids, or all with search / company / from_list. Contacts are not deleted, only taken off the list.
| Parameter | Description |
|---|---|
|
contact_ids
uuid[]
optional
|
Specific contacts to remove. |
|
all
boolean
optional
|
Select every member (narrowed by the filters). Required unless contact_ids is given. |
modelpad lists remove pricing-enquiries --all --search "@old-domain.example"
Orders
Purchases, created by checkout rather than by you. Read-only apart from fulfilment status.
List orders
| Parameter | Description |
|---|---|
|
status
string
optional
|
Filter by fulfilment status. |
|
page
integer
optional
|
1-based page number. |
|
limit
integer
optional
|
Records per page. |
modelpad orders list
{
"orders": [
{
"id": "a87ff679-…",
"email": "[email protected]",
"total_cents": 14900,
"currency": "usd",
"status": "paid",
"created_at": "2026-08-01T16:41:09Z"
}
],
"pagination": { "page": 1, "limit": 50, "total": 63, "has_more": true }
}
Update fulfilment status
For physical goods — mark an order shipped once it leaves.
| Parameter | Description |
|---|---|
|
status
string
Required
|
New fulfilment status. |
modelpad api PATCH /api/orders/a87ff679-…/status --set status=shipped
Revenue summary
Aggregate revenue, subscriber and order figures behind the seller dashboard.
modelpad api GET /api/dashboard/summary
Experiments
Block-level A/B tests. An experiment attaches to one block on one page and splits traffic between weighted variants, counting impressions and conversions.
List experiments
| Parameter | Description |
|---|---|
|
status
string
optional
|
e.g. running, stopped. |
modelpad experiments list --query status=running
Retrieve results
Impressions and conversions per variant. Conversions count against the same records the rest of the platform uses, so a result is expressed in orders rather than clicks.
modelpad experiments stats <id>
{
"stats": [
{ "variant_id": "control", "impressions": 4120, "conversions": 96 },
{ "variant_id": "b", "impressions": 4088, "conversions": 131 }
]
}
List variants
modelpad experiments variants <id>
Access & tiers
Who can see what. An access rule binds an item to a way of reaching it — free, registration, one-time purchase, or a subscription tier. An item can carry several.
List access rules
modelpad access-rules list
Create or update an access rule
| Parameter | Description |
|---|---|
|
item_id
uuid
Required
|
The note, page or product being gated. |
|
item_type
string
Required
|
notes, landing_pages or products. |
|
rule_type
string
Required
|
How access is granted: free, registered, purchase or tier. |
|
tier_id
uuid
optional
|
Required when rule_type is tier. |
modelpad access-rules create --file rule.json
List subscription tiers
modelpad tiers list
List collections
Bundles of items sold or granted together.
modelpad collections list
Taxonomy
Facets and their values — the attributes visitors filter a catalogue by. One taxonomy covers products and notes alike: define the facet once, then assign values to products and notes, and the Content Browser block filters both together.
List facets
modelpad facets list
Create or update a facet
| Parameter | Description |
|---|---|
|
name
string
Required
|
Display name, e.g. Material. |
|
slug
string
optional
|
Query-string key on catalogue URLs. |
modelpad facets create --name Material
Reorder facet values
Sets the order values appear in filter UI.
Set a note's taxonomy values
Replaces the note's full set of values; GET the same path reads it. Products use /api/products/:id/taxonomy with the same body.
| Parameter | Description |
|---|---|
|
value_ids
array of uuid
Required
|
Every value the note should carry; omit one to unassign it. |
modelpad notes set-taxonomy 4b1c…-… --file values.json
Browse a public catalogue
Unauthenticated faceted search over an account's public products and notes — what the Content Browser block calls. Returns items, pagination and facet counts.
| Parameter | Description |
|---|---|
|
types
string
optional
|
Comma-separated kinds: product, note. Defaults to product. |
|
facets
string
optional
|
Comma-separated taxonomy value ids; OR within a facet, AND across facets. |
|
search
string
optional
|
Substring match on titles and descriptions. |
|
page
integer
optional
|
1-based page number. |
|
limit
integer
optional
|
Items per page, at most 60. |
Search, keys & the rest
Cross-cutting endpoints, and the ones that manage the credential you are using.
Search across everything
One query over notes, pages, products, contacts, media and more. This is what the app's command palette calls.
| Parameter | Description |
|---|---|
|
q
string
Required
|
Search text. |
|
limit
integer
optional
|
Maximum results. |
curl "https://modelpad.app/api/search/entities?q=procurement&limit=5" \ -H "Authorization: Bearer $MODELPAD_TOKEN"
modelpad search procurement
{
"results": [
{ "id": "8f14e45f-…", "title": "Procurement checklist",
"type": "notes", "subtitle": "guides" }
]
}
The current account
Resolves the credential to an account. Handy as a health check for an integration.
modelpad whoami
{
"user": {
"id": "0cc175b9-…",
"email": "[email protected]",
"account_name": "northwind",
"is_admin": false
}
}
List API keys
Metadata only — the key itself is shown once, at creation.
modelpad keys list
Create an API key
Returns the raw key exactly once. Store it before you close the response.
| Parameter | Description |
|---|---|
|
label
string
optional
|
How the key shows up in the list. |
modelpad api POST /api/keys --set label="ci pipeline"
Revoke an API key
Immediate. Requests using it start failing with 401.
modelpad keys delete <id>
Storefront settings
Branding, custom domain and which items appear on the public storefront.
modelpad api GET /api/storefront-settings
Something missing?
The reference covers the resources most integrations reach for. Every
other endpoint the web app uses is reachable with the same credential —
modelpad api <METHOD> <path> from the CLI, or a
plain HTTP request with your bearer token.