ModelPad API Reference Home Get API keys
ModelPad

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.

bash
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.

Keep keys server-side. A key carries the full rights of the account. Do not ship one in a browser bundle or a mobile app — put your own endpoint in front of it.
bash
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.

StatusMeaning
200Success.
400Malformed JSON, or a field failed validation.
401Missing or invalid credential.
403Authenticated, but not permitted — an admin-only operation, or content you have not purchased.
404No such record for this account.
413Upload exceeded the size limit for its media type.
500Something failed our side.
json
{
  "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.

json
{
  "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.

Deleting. References between records live inside opaque block JSON that no endpoint indexes, so nothing warns you that a page still points at the product you are about to remove. Run modelpad usage <id-or-slug> first.
bash
# 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.

bash
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
Resource

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

GET /api/sites/:id/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.

CLI
modelpad sites overview ember-and-salt
Response
{
  "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

GET /api/sites/:id/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.

CLI
modelpad sites brief primary

Write the editorial brief

PATCH /api/sites/:id/brief

Sets the fields sent and keeps the rest. An empty string clears a field; an unknown field is refused by name.

ParameterDescription
niche, audience, voice, …
string
optional
Any of the brief's fields.
CLI
modelpad sites set-brief primary --set voice="Plain, first person, no hype"

What a site publishes

GET /api/sites/:id/content

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.

ParameterDescription
public
boolean
optional
false includes unpublished notes.
CLI
modelpad sites content ember-and-salt
modelpad sites update ember-and-salt --set 'content_facets=["Category=Recipes"]'

Pin a note to a site

PATCH /api/notes/:id/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.

ParameterDescription
site
string
Required
Site id or slug; empty to unpin.
CLI
modelpad notes set-site <note id> --set site=ember-and-salt

Site traffic

GET /api/sites/:id/stats

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".

ParameterDescription
days
integer
optional
Length of the period. Defaults to 30; at most 730.
CLI
modelpad sites stats primary --query days=90

Every article with its numbers

GET /api/sites/:id/stats/content

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.

ParameterDescription
days
integer
optional
Defaults to 30.
sort
string
optional
views (default), least, visitors, read_rate, click_through_rate, avg_seconds, published.
CLI
modelpad sites content-stats primary --query sort=least

One article's readership

GET /api/notes/:id/stats

Totals and trend for one note, its referrers, the pages of the site that sent readers to it, and where they went next.

ParameterDescription
days
integer
optional
Defaults to 90.
CLI
modelpad notes stats <note id>

Notes that need an editor

GET /api/notes/lint

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.

ParameterDescription
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.
CLI
modelpad notes lint --query site=primary --query shared=true --query unedited=true

Repair encoding damage

POST /api/notes/fix-encoding

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.

ParameterDescription
dry_run
boolean
optional
Defaults to true: report what would change.
ids
array
optional
Limit the repair to these note ids.
CLI
modelpad notes fix-encoding
modelpad notes fix-encoding --set dry_run=false

Test an article's title

PUT /api/experiments/:id

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.

ParameterDescription
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.
CLI
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

POST /api/themes/check

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.

CLI
modelpad themes check my-theme
modelpad themes push my-theme

Redirect old addresses

PUT /api/sites/:id/redirects

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.

ParameterDescription
redirects
array
Required
[{"from": "/blog/*", "to": "/doc/*"}]
CLI
modelpad sites add-redirect <site> --set from=/blog/* --set to=/doc/*

Site icon and share image

PUT /api/sites/:id

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.

CLI
modelpad sites update <site> --set favicon_media_id=<media id> --set share_image_media_id=<media id>

Connect Search Console

PUT /api/sites/:id/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.

ParameterDescription
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.
CLI
modelpad sites search-connect primary --file service-account.json --set property=sc-domain:example.com
Resource

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

GET /api/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).

ParameterDescription
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.
Request
curl https://modelpad.app/api/notes?limit=2 \
  -H "Authorization: Bearer $MODELPAD_TOKEN"
CLI
modelpad notes list --query limit=2
Response
{
  "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

GET /api/notes/:id

Returns one note including its full body as Lexical JSON. Add ?format=markdown to also get body_markdown, the body rendered as markdown.

ParameterDescription
format
string
optional
markdown — include body_markdown alongside the Lexical body.
Request
curl https://modelpad.app/api/notes/8f14e45f-… \
  -H "Authorization: Bearer $MODELPAD_TOKEN"
CLI
modelpad notes get 8f14e45f-…
modelpad notes get 8f14e45f-… --as-markdown

Create or update a note

PUT /api/notes/:id

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.

ParameterDescription
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.
Request
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"}'
CLI
modelpad notes create --title "Procurement checklist" --markdown checklist.md

Publish or unpublish

PATCH /api/notes/:id/share

Toggles whether the note is served at its public URL. Publishing is what makes it indexable.

ParameterDescription
is_shared
boolean
Required
True to publish.
Request
curl -X PATCH https://modelpad.app/api/notes/8f14e45f-…/share \
  -H "Authorization: Bearer $MODELPAD_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"is_shared": true}'
CLI
modelpad notes share 8f14e45f-… --set is_shared=true

Read search and share metadata

GET /api/notes/:id/seo

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.

Request
curl https://modelpad.app/api/notes/8f14e45f-…/seo   -H "Authorization: Bearer $MODELPAD_TOKEN"
CLI
modelpad notes seo 8f14e45f-…
Response
{
  "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

PATCH /api/notes/:id/seo

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.

ParameterDescription
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.
Request
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."}'
CLI
modelpad notes set-seo 8f14e45f-… --set slug=procurement-checklist --set meta_description="A four-step checklist…"

List child notes

GET /api/notes/:id/children

Direct children only. Use /descendants for the whole subtree.

CLI
modelpad notes children 8f14e45f-…

Move a note

PATCH /api/notes/:id/parent

Reparents the note. Pass a null parent to move it to the top level.

CLI
modelpad notes move 8f14e45f-… --set parent=<uuid>

Delete a note

DELETE /api/notes/:id

Permanent. Check what references it first.

CLI
modelpad usage 8f14e45f-… && modelpad notes delete 8f14e45f-…
Resource

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

GET /api/landing-pages

All landing pages except the home page, which has its own endpoint.

CLI
modelpad pages list
Response
{
  "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

GET /api/landing-pages/home

The page served at the account root. Provisioned on first access, so this never 404s.

CLI
modelpad pages home

Create or update a page

PUT /api/landing-pages/:id

Upserts the page. Blocks are replaced wholesale — read, modify, write back.

ParameterDescription
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.
Request
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" } }
    ]
  }'
CLI
modelpad pages update 3c6e0b8a-… --set 'blocks=[{"section_type":"hero","props":{}}]'

List a page's blocks

GET /api/landing-pages/:id/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.

CLI
modelpad pages blocks <page>

Add a block

POST /api/landing-pages/:id/blocks

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.

ParameterDescription
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.
CLI
modelpad pages add-block <page> hero --set heading="Hello"

Change a block

PATCH /api/landing-pages/:id/blocks/: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).

CLI
modelpad pages set-block <page> hero --set subtitle="New" --set style.scheme=dark

Move a block

POST /api/landing-pages/:id/blocks/:block/move

Body {at} is the 1-based position to move it to.

CLI
modelpad pages move-block <page> <block> --at 1

Remove a block

DELETE /api/landing-pages/:id/blocks/:block

A block that is part of an A/B test is refused unless ?force=true.

CLI
modelpad pages remove-block <page> <block>

A site's template pages

GET /api/landing-pages/template/:kind

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.

CLI
modelpad pages blocks header

Render blocks without saving

POST /api/landing-pages/preview

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.

CLI
modelpad api POST /api/landing-pages/preview --data '{"blocks":[]}'

Make a page the home page

POST /api/landing-pages/:id/set-home

Promotes this page to the account root. The previous home page becomes an ordinary page.

CLI
modelpad pages set-home 3c6e0b8a-…
Resource

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

GET /api/section-types

System types plus any this account owns, templates included.

CLI
modelpad blocks list

The block catalog

GET /api/blocks

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.

CLI
modelpad blocks catalog

Create or update a block type

PUT /api/section-types/:id

Defines a reusable block. The template is Go html/template with the block's props in scope.

ParameterDescription
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.
CLI
modelpad blocks update <id> --file block.json

Entity fields available to templates

GET /api/entity-fields

What a block template can read off a linked record — useful when authoring prop schemas that point at products, notes or media.

CLI
modelpad blocks fields
Resource

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

GET /api/sites

Every site with its home page id, page and domain counts, and its theme ("" means the default built-in).

CLI
modelpad sites list

Update a site

PUT /api/sites/:id

A patch: only the fields sent change. Setting theme restyles every page, article and product page on the site.

ParameterDescription
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.
CLI
modelpad sites update <site id> --set theme=builtin:editorial

List themes

GET /api/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).

CLI
modelpad themes list

What a theme can style

GET /api/themes/reference

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.

CLI
modelpad themes reference

Create a theme

POST /api/themes

Starts from the defaults, or from another theme with copy_from.

ParameterDescription
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.
CLI
modelpad themes create --name "Blog" --set copy_from=builtin:turing-registry

Update a theme

PUT /api/themes/:id

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.

ParameterDescription
name
string
optional
Display name.
tokens
object
optional
Tokens to change.
custom_css
string
optional
Replaces the theme's custom CSS.
CLI
modelpad themes update <id> --set tokens.accent=#0af --css theme.css

A theme's stylesheet

GET /api/themes/:id/css

The complete CSS a page under this theme carries: tokens, block styles, then the theme's custom CSS. Returned as text/css.

CLI
modelpad themes css builtin:signature

Delete a theme

DELETE /api/themes/:id

Sites using it return to the default theme.

CLI
modelpad themes delete <id>
Resource

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

GET /api/media

Note the array key is media_items.

ParameterDescription
search
string
optional
Match against name and description.
page
integer
optional
1-based page number.
limit
integer
optional
Records per page, max 200.
CLI
modelpad media list
Response
{
  "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

PUT /api/media/:id

Step one of an upload: reserve the id and set the name. The record exists with no file until you upload one.

CLI
modelpad media create --set id=<uuid> --set name=warehouse.jpg

Upload the file

POST /api/media/:id/upload

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.

ParameterDescription
file
file
Required
The file, as multipart/form-data.
Request
curl -X POST https://modelpad.app/api/media/b1946ac9-…/upload \
  -H "Authorization: Bearer $MODELPAD_TOKEN" \
  -F "[email protected]"
Response
{
  "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

POST /api/media/:id/regenerate-thumbnails

Re-derives image variants from the stored original. Rejects non-images with 400.

CLI
modelpad media regenerate-thumbnails b1946ac9-…
Resource

Products

Everything you sell: physical goods, digital downloads, and access passes. Products carry taxonomy values so catalogues can be filtered.

List products

GET /api/products
CLI
modelpad products list
Response
{
  "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

PUT /api/products/:id
ParameterDescription
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.
CLI
modelpad products create --name "Compliance Toolkit" --set price=149.00

List attached files

GET /api/products/:id/downloads

The media records a buyer receives after purchase.

CLI
modelpad products downloads d3d94468-…

Attach a file

POST /api/products/:id/downloads

Links a media record to the product as a deliverable. Buyers get signed, expiring links.

ParameterDescription
media_id
uuid
Required
The media record to deliver.
CLI
modelpad products add-download d3d94468-… --set media_id=<uuid>

Set taxonomy values

PUT /api/products/:id/taxonomy

Assigns facet values so the product appears under the right catalogue filters.

CLI
modelpad products set-taxonomy d3d94468-… --file values.json
Resource

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

GET /api/contacts
ParameterDescription
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.
Request
curl "https://modelpad.app/api/contacts?list=pricing-enquiries" \
  -H "Authorization: Bearer $MODELPAD_TOKEN"
CLI
modelpad contacts list --query list=pricing-enquiries
Response
{
  "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

PUT /api/contacts/:id
ParameterDescription
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.
Request
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]"}'
CLI
modelpad contacts create --name "Dana Whitfield" --set [email protected]

List interactions

GET /api/contact-activities

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.

ParameterDescription
contact_id
uuid
optional
Restrict to one contact.
CLI
modelpad contacts activities c9f0f895-…

List transactions

GET /api/contact-transactions

What this contact has paid you — useful for building an account view.

CLI
modelpad api GET /api/contact-transactions --query contact_id=c9f0f895-…

Add an internal note

POST /api/contact-notes

Freeform note attached to a contact. Never shown publicly.

CLI
modelpad api POST /api/contact-notes --set contact_id=<uuid> --set body="Asked about volume pricing"
Resource

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

GET /api/contact-lists

Includes a member count per list.

CLI
modelpad lists list
Response
{
  "contact_lists": [
    {
      "id": "6c8349cc-…",
      "name": "Pricing enquiries",
      "slug": "pricing-enquiries",
      "member_count": 148
    }
  ]
}

Create or update a list

PUT /api/contact-lists/:id
ParameterDescription
name
string
Required
Display name. The slug derives from it when not given.
slug
string
optional
What a lead form block references.
CLI
modelpad lists create --name "Pricing enquiries"

List members

GET /api/contact-lists/:id/members
CLI
modelpad lists members pricing-enquiries

Add a contact to a list

POST /api/contact-lists/:id/members
ParameterDescription
contact_id
uuid
Required
The contact to add.
CLI
modelpad lists add pricing-enquiries <uuid>

Remove a contact from a list

DELETE /api/contact-lists/:id/members/:contactId

Removes the membership. The contact itself is untouched.

Add many contacts at once

POST /api/contact-lists/:id/members/bulk

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.

ParameterDescription
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.
Request
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"}'
CLI
modelpad lists add pricing-enquiries --all --company Northwind
Response
{
  "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

POST /api/contact-lists/:id/members/remove

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.

ParameterDescription
contact_ids
uuid[]
optional
Specific contacts to remove.
all
boolean
optional
Select every member (narrowed by the filters). Required unless contact_ids is given.
CLI
modelpad lists remove pricing-enquiries --all --search "@old-domain.example"
Resource

Orders

Purchases, created by checkout rather than by you. Read-only apart from fulfilment status.

List orders

GET /api/orders
ParameterDescription
status
string
optional
Filter by fulfilment status.
page
integer
optional
1-based page number.
limit
integer
optional
Records per page.
CLI
modelpad orders list
Response
{
  "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

PATCH /api/orders/:id/status

For physical goods — mark an order shipped once it leaves.

ParameterDescription
status
string
Required
New fulfilment status.
CLI
modelpad api PATCH /api/orders/a87ff679-…/status --set status=shipped

Revenue summary

GET /api/dashboard/summary

Aggregate revenue, subscriber and order figures behind the seller dashboard.

CLI
modelpad api GET /api/dashboard/summary
Resource

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

GET /api/experiments
ParameterDescription
status
string
optional
e.g. running, stopped.
CLI
modelpad experiments list --query status=running

Retrieve results

GET /api/experiments/:id/stats

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.

CLI
modelpad experiments stats <id>
Response
{
  "stats": [
    { "variant_id": "control", "impressions": 4120, "conversions": 96 },
    { "variant_id": "b",       "impressions": 4088, "conversions": 131 }
  ]
}

List variants

GET /api/experiments/:id/variants
CLI
modelpad experiments variants <id>
Resource

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

GET /api/access-rules
CLI
modelpad access-rules list

Create or update an access rule

PUT /api/access-rules/:id
ParameterDescription
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.
CLI
modelpad access-rules create --file rule.json

List subscription tiers

GET /api/tiers
CLI
modelpad tiers list

List collections

GET /api/collections

Bundles of items sold or granted together.

CLI
modelpad collections list
Resource

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

GET /api/taxonomy/facets
CLI
modelpad facets list

Create or update a facet

PUT /api/taxonomy/facets/:id
ParameterDescription
name
string
Required
Display name, e.g. Material.
slug
string
optional
Query-string key on catalogue URLs.
CLI
modelpad facets create --name Material

Reorder facet values

PUT /api/taxonomy/facets/:id/values/order

Sets the order values appear in filter UI.

Set a note's taxonomy values

PUT /api/notes/:id/taxonomy

Replaces the note's full set of values; GET the same path reads it. Products use /api/products/:id/taxonomy with the same body.

ParameterDescription
value_ids
array of uuid
Required
Every value the note should carry; omit one to unassign it.
CLI
modelpad notes set-taxonomy 4b1c…-… --file values.json

Browse a public catalogue

GET /api/browse/:account/items

Unauthenticated faceted search over an account's public products and notes — what the Content Browser block calls. Returns items, pagination and facet counts.

ParameterDescription
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.
Resource

Search, keys & the rest

Cross-cutting endpoints, and the ones that manage the credential you are using.

Search across everything

GET /api/search/entities

One query over notes, pages, products, contacts, media and more. This is what the app's command palette calls.

ParameterDescription
q
string
Required
Search text.
limit
integer
optional
Maximum results.
Request
curl "https://modelpad.app/api/search/entities?q=procurement&limit=5" \
  -H "Authorization: Bearer $MODELPAD_TOKEN"
CLI
modelpad search procurement
Response
{
  "results": [
    { "id": "8f14e45f-…", "title": "Procurement checklist",
      "type": "notes", "subtitle": "guides" }
  ]
}

The current account

GET /api/auth/me

Resolves the credential to an account. Handy as a health check for an integration.

CLI
modelpad whoami
Response
{
  "user": {
    "id": "0cc175b9-…",
    "email": "[email protected]",
    "account_name": "northwind",
    "is_admin": false
  }
}

List API keys

GET /api/keys

Metadata only — the key itself is shown once, at creation.

CLI
modelpad keys list

Create an API key

POST /api/keys

Returns the raw key exactly once. Store it before you close the response.

ParameterDescription
label
string
optional
How the key shows up in the list.
CLI
modelpad api POST /api/keys --set label="ci pipeline"

Revoke an API key

DELETE /api/keys/:id

Immediate. Requests using it start failing with 401.

CLI
modelpad keys delete <id>

Storefront settings

GET /api/storefront-settings

Branding, custom domain and which items appear on the public storefront.

CLI
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.

Create an account