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
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, newest first.
| Parameter | Description |
|---|---|
|
search
string
optional
|
Full-text match against title and body. |
|
parent
uuid
optional
|
Only direct children of this note. |
|
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.
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.
| Parameter | Description |
|---|---|
|
title
string
Required
|
Display title. |
|
body
object
optional
|
Lexical document JSON. |
|
slug
string
optional
|
URL segment for the public page. Derived from the title when omitted. |
|
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","tags":["guides"]}'
modelpad notes create --title "Procurement checklist" --markdown checklist.md
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":{}}]'
Render blocks without saving
Renders an arbitrary block array to HTML. This is what the builder's preview pane calls; it returns a fragment, not a full document.
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.
modelpad blocks list
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
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. |
|
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
Form submissions and other recorded touches. Filter to one person with contact_id.
| Parameter | Description |
|---|---|
|
contact_id
uuid
optional
|
Restrict to one contact. |
modelpad contacts interactions 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 --set contact_id=<uuid>
Remove a contact from a list
Removes the membership. The contact itself is untouched.
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 buyers filter a catalogue by. Define the facet once, then assign values to products.
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.
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.