Developers
API Reference
The Moxial API lets you create the same self-contained mockup documents the editor produces — programmatically. Pick a template, send your content, and get back a fully-resolved mockup you can open, refine, and export from the app.
- Base URL:
https://mxl.on-forge.com/api/v1 - All requests and responses are JSON.
- Authentication:
Authorization: Bearer <token>— see below.
Authentication
Every endpoint requires a personal access token. Create one in Settings → API Access — the plain-text token is shown once at creation. Send it on every request:
curl https://mxl.on-forge.com/api/v1/templates \
-H "Authorization: Bearer <your-token>" \
-H "Content-Type: application/json"
Tokens act on behalf of your account and workspace: mockups created through the API belong to your team, appear in your library, and can only be read back by your own tokens. Revoking a token in Settings immediately invalidates it.
Errors & rate limits
The API always answers JSON, even without an Accept header.
Requests are limited to 60 per minute per token.
| Status | Meaning | Body |
|---|---|---|
401 | Missing, invalid, or revoked token. | {"message": "Unauthenticated."} |
404 | Resource not found — including mockups that belong to another workspace. | {"message": "…"} |
422 | Validation failed (unknown template, unsupported device, content that violates the template schema…). | {"message": "…", "errors": {"field": ["…"]}} |
429 | Rate limit exceeded — retry after the indicated delay. | {"message": "Too Many Attempts."} |
GET/api/v1/templates
Lists the available templates. Use it to discover the id to pass as
template_id when creating a mockup, and the devices, color schemes,
and locales each template supports.
{
"data": [
{
"id": "665f2a1b8c9d4e0012345678",
"name": "X Single 2025",
"platform": "x",
"view": "single",
"archetype": "post",
"year": 2025,
"device_ids": ["iphone15", "iphone15plus", "iphone15pro", "iphone15promax", "iphone16", "iphone16plus", "iphone16pro", "iphone16promax", "iphone17", "iphone17e", "iphone17pro", "iphone17promax", "iphoneair", "desktop"],
"color_schemes": ["light", "dark"],
"locales": ["en", "fr"],
"description": "X (Twitter) single mockup for 2025"
}
]
}
| Field | Type | Description |
|---|---|---|
id | string | Template id — pass it as template_id to POST /mockups. |
platform | string | x or instagram. |
view | string | The rendered surface: single (one post) or timeline (a scrollable feed). |
archetype | string | The content shape: post (fields at the content root) or timeline (a posts array). See the content payload. |
year | int | The era the UI reproduces (fonts, chrome, badges of that year). |
device_ids | string[] | Devices the template can render in — valid values for device_value. |
color_schemes / locales | string[] | Valid values for presentation.color_scheme / presentation.locale. |
GET/api/v1/templates/{id}
One template, including its field_schema — the machine-readable
contract for the content payload. The schema is a list of
groups, each with typed fields
(field types), and every field carries a one-line
description of what it renders. Your content is validated against
this exact schema on creation, so you can generate forms or payloads from it instead of hard-coding the field lists below.
{
"id": "665f2a1b8c9d4e0012345678",
"name": "X Single 2025",
"platform": "x",
"view": "single",
"archetype": "post",
"year": 2025,
"device_ids": ["iphone15", "iphone15plus", "iphone15pro", "iphone15promax", "iphone16", "iphone16plus", "iphone16pro", "iphone16promax", "iphone17", "iphone17e", "iphone17pro", "iphone17promax", "iphoneair", "desktop"],
"color_schemes": ["light", "dark"],
"locales": ["en", "fr"],
"description": "X (Twitter) single mockup for 2025",
"field_schema": {
"groups": [
{ "key": "post", "label": "Post", "fields": [
{ "key": "text", "type": "text", "label": "Text", "max": 1000 },
{ "key": "media", "type": "media[]", "label": "Media", "max": 4 }
] }
]
}
}
POST/api/v1/mockups
Creates a mockup in one call. Only template_id is required:
Moxial resolves the default device, operating system, color scheme, and locale, binds your default
profile as the author, and seeds the content with the template defaults. Everything else is an
optional override merged on top — omitted content keys keep their seeded values.
Request body
| Field | Type | Description |
|---|---|---|
template_id | string, required | Id of an available template (GET /templates). |
name | string, optional, ≤ 255 | Library label. Defaults to the template name. |
device_value | string, optional | Device frame — must be one of the template's device_ids (422 otherwise). Defaults to the iPhone 15 Pro Max when supported. The matching OS (status bar, clock) is derived automatically. |
viewer_profile_id | string, optional | Id of one of your saved Profiles. It is snapshotted as the mockup's viewer (the identity in the platform chrome) and as the default post author. Defaults to your workspace's default profile. |
content | object, optional | The post data, validated against the template's field_schema and merged onto the seeded defaults. See the content payload. |
presentation | object, optional | Framing and styling overrides (color scheme, locale, status bar, export options). Top-level keys are replaced wholesale. See the presentation object. |
Example — an X post
curl -X POST https://mxl.on-forge.com/api/v1/mockups \
-H "Authorization: Bearer <your-token>" \
-H "Content-Type: application/json" \
-d '{
"template_id": "665f2a1b8c9d4e0012345678",
"name": "Launch announcement",
"device_value": "iphone16pro",
"content": {
"author": {
"name": "Ada Lovelace",
"username": "adalovelace",
"avatar": "https://example.com/ada.png",
"verification": "blue"
},
"text": "Just shipped our new analytics dashboard 🚀",
"media": [
{ "type": "image", "url": "https://example.com/dashboard.png", "aspect": "16/9" }
],
"posted_at": "2025-07-17T09:41",
"likes_count": 1284,
"reposts_count": 96,
"comments_count": 47,
"views_count": 38200
},
"presentation": { "color_scheme": "dark" }
}'
Returns 201 Created with
the mockup document.
GET/api/v1/mockups/{id}
Retrieves one of your mockups as the mockup document — the same shape
POST /mockups returns. Mockups belonging to another workspace
answer 404.
The content payload
content is archetype-shaped:
- post (single views) — the post's fields sit at the content root, next to an
authorobject. - timeline (feed views) — the content root holds one
postsarray; each item carries the same per-post fields, including its ownauthor(every post can be a different account).
Every field is optional — anything you omit keeps the template's seeded default. Unknown keys are
rejected-by-omission: only fields declared in the template's field_schema
survive validation.
X — post fields
Used at the content root of X Single and inside each posts[] item of X Timeline.
| Field | Type | Description |
|---|---|---|
author | profile | The post's author — see the X author object. Defaults to a snapshot of your (or the bound) profile. |
text | string ≤ 1000 | The post body. Emoji supported. |
media | media[] ≤ 4 | Attached images/videos — see the media object. |
posted_at | datetime | Publication date shown on the post, e.g. "2025-07-17T09:41". |
is_repost | bool | Renders the "reposted" banner above the post. |
is_sponsored | bool | Marks the post as an ad. |
poll_enabled | bool | Attach a poll. The poll_* fields below only render when this is true. |
poll_show_results | bool | Show vote percentages instead of the voting buttons. |
poll_end | string | The countdown label, e.g. "1 day left". |
poll_options | list ≤ 4 | Array of { "text": string, "votes_count": int }. |
quote_enabled | bool | Embed a quoted post. The quote_* fields below only render when this is true. |
quote_author | profile | Quoted post's author — identity subset only: name, username, avatar, verification. |
quote_text | string ≤ 1000 | Quoted post's body. |
quote_media | media[] ≤ 4 | Quoted post's media. |
quote_posted_at | datetime | Quoted post's date. |
likes_count, reposts_count, comments_count, views_count, saves_count | int | Engagement counters (rendered with K/M abbreviation like the real UI). |
viewer_liked, viewer_reposted, viewer_saved | bool | Render the action buttons in their active (colored) state. |
reply_field, reply_field_focused, reply | bool / bool / string ≤ 1000 | X Single only — show the reply composer under the post, optionally focused, with a drafted reply. |
X — the author object
| Field | Type | Description |
|---|---|---|
name / username | string | Display name and handle (without the @). |
avatar | string (URL) | Avatar image URL. |
verification | enum | none · blue (Verified) · gov (Government, grey) · org (Organization, gold). |
affiliation_logo | string (URL) | Small affiliated-organization logo shown next to the badge (verified accounts only). |
is_protected, viewer_follows, viewer_subscribed, is_live | bool | Identity chrome: padlock, "Following" state, subscriber badge, live ring. |
profile_id | string | null | Provenance of a snapshot taken from a saved Profile — leave it out for hand-written authors. |
Instagram — post fields
Used inside each posts[] item of Instagram Timeline.
| Field | Type | Description |
|---|---|---|
author | profile | name, username, avatar (URL), and is_verified (bool — the blue badge). |
media | media[] ≤ 10 | The post's image(s)/video(s); several items render as a carousel. |
text | string ≤ 2200 | The caption. Emoji and hashtags supported. |
posted_at | datetime | Publication date. |
likes_count / comments_count | int | Engagement counters. |
viewer_liked / viewer_saved | bool | Render the heart / bookmark in their active state. |
Timelines — the posts array
For timeline templates, wrap the per-post objects in
content.posts. Sending posts replaces the whole
array (it is not merged item-by-item):
{
"template_id": "<x-timeline-template-id>",
"content": {
"posts": [
{
"author": { "name": "Acme", "username": "acme", "verification": "org" },
"text": "We are hiring! Join a team that ships.",
"likes_count": 210, "reposts_count": 18, "comments_count": 9, "views_count": 12400
},
{
"author": { "name": "Jane Doe", "username": "janedoe", "verification": "blue" },
"text": "Hot take: dashboards are the new landing pages.",
"media": [ { "type": "image", "url": "https://example.com/chart.png" } ],
"likes_count": 3400, "reposts_count": 240, "comments_count": 85, "views_count": 96000
}
]
}
}
Field types
Every field in a field_schema uses one of these types — the closed
registry the validator enforces:
| Type | JSON shape | Validation |
|---|---|---|
text | string | null | Max length from the field's max (e.g. 1000 for an X post). |
int | integer | null | Counters (likes, votes…). |
bool | boolean | Toggles (liked, verified, poll_enabled…). |
enum | string | null | Must be one of the field's options (e.g. verification). |
datetime | string | null | Any parseable date; the editor uses YYYY-MM-DDTHH:mm. |
media[] | array of media objects | Max items from the field's max. See the media object. |
list | array of objects | Each item is validated against the list's item fields (e.g. posts, poll_options). |
profile | object | null | An author snapshot; its scalar keys pass through as-is. |
The media object
{ "type": "image", "url": "https://example.com/visual.png", "aspect": "16/9" }
| Field | Type | Description |
|---|---|---|
type | enum, required | image · video · gif. |
url | string, required | Publicly reachable media URL. Private/internal hosts are blanked at export time. |
aspect | string, optional | Aspect-ratio hint such as "16/9" or "1/1". |
The presentation object
Controls framing and styling around the content. Every key is optional; top-level keys you send replace the resolved defaults wholesale.
| Key | Type | Description |
|---|---|---|
color_scheme | enum | light or dark (must be in the template's color_schemes). |
locale | string | Interface language of the platform chrome (from the template's locales, e.g. en, fr). |
device_settings | object | Status-bar values: hour (string), battery (string), wifi (0–3), cellular (0–4), location, focus_mode. |
interface | object | Platform chrome: notifications_count (int|null), dms_count (int|null), show_home_dot (bool), show_profile_dot (bool). |
export | object | Export defaults: frame (show the device bezel), device_ui (status bar + home indicator), scale (1–3). |
The mockup document (response)
Both mockup endpoints return the fully-resolved document — everything the renderer needs, frozen.
The viewer and content
blocks are snapshots: editing a saved Profile later never rewrites an existing mockup.
{
"id": "6672be40a1b2c3d4e5f60789",
"name": "Launch announcement",
"viewer_profile_id": "665f9f001122334455667788",
"template": {
"id": "665f2a1b8c9d4e0012345678",
"platform": "x",
"view": "single",
"archetype": "post",
"year": 2025,
"renderer": "platforms-x-2025-single",
"field_schema": { "groups": [ … ] }
},
"device": { "value": "iphone16pro", "family": "ios", "frame": { … } },
"system": { "slug": "ios-18", "family": "ios", "major": 18, "status_bar": { … } },
"system_slug": "ios-18",
"presentation": {
"color_scheme": "dark",
"locale": "en",
"device_settings": { "hour": "9:41", "battery": "100%", "wifi": 3, "cellular": 4 },
"interface": { "notifications_count": null, "dms_count": null, "show_home_dot": false, "show_profile_dot": false },
"export": { "frame": true, "device_ui": true, "scale": 3 }
},
"viewer": { "profile_id": "665f9f00…", "name": "Ada Lovelace", "username": "adalovelace", "avatar": "…" },
"content": {
"author": { "name": "Ada Lovelace", "username": "adalovelace", "verification": "blue" },
"text": "Just shipped our new analytics dashboard 🚀",
"media": [ { "type": "image", "url": "https://example.com/dashboard.png", "aspect": "16/9" } ],
"posted_at": "2025-07-17T09:41",
"likes_count": 1284, "reposts_count": 96, "comments_count": 47, "views_count": 38200, "saves_count": 58
}
}
| Field | Description |
|---|---|
id | The mockup id — use it with GET /api/v1/mockups/{id}, or open /mockups/{id}/edit in the app. |
template | The rendering reference: platform, view, archetype, era (year), renderer component, and the field_schema the content was validated against. |
device | The device frame: value, family (ios/web), and bezel geometry (frame). |
system / system_slug | The resolved OS whose status_bar drives era-correct chrome — null for the web/desktop frame. |
presentation | The resolved presentation object (your overrides merged over the defaults). |
viewer | Frozen — the interface identity shown in the platform chrome (e.g. the X timeline header avatar). |
content | Frozen — the full post/timeline payload (your overrides merged over the seeded defaults). |
viewer_profile_id | Provenance: which saved Profile the viewer was snapshotted from. |
MCP server
Moxial also speaks the Model Context Protocol, so AI clients (Claude, Cursor, and other MCP-compatible agents) can create mockups conversationally. Connect over streamable HTTP with OAuth — add the URL and your client walks you through signing in and approving access. No token to copy:
https://mxl.on-forge.com/mcp
# e.g. Claude Code:
claude mcp add --transport http moxial https://mxl.on-forge.com/mcp
The client registers itself automatically (dynamic client registration) and uses the authorization-code flow with PKCE. Approved applications are listed under Settings → AI clients, where you can revoke access at any time. API tokens are for the REST endpoints above — they do not authenticate MCP.
The server covers the full loop — discover, create, iterate, render:
| Tool | Description |
|---|---|
list-templates | Template discovery — same payload as GET /templates. |
get-template | One template with its field_schema — same payload as GET /templates/{id}. |
list-profiles | Your saved author/viewer identities — pass an id as viewer_profile_id. |
create-mockup | Same arguments as POST /mockups; returns the mockup document plus an edit_url to open it in the editor. |
update-mockup | Iterate on an existing mockup — partial content / presentation merges, device switch, rename. |
list-mockups | Your mockups, newest first (workspace-scoped), each with its edit_url. |
get-mockup | Retrieve one of your mockups (workspace-scoped), plus its edit_url. |
export-mockup | Render the final pixel-accurate PNG (scale 1–3) and return it as an image, straight into the conversation. |
It also publishes two resources — moxial://guide (workflow guide) and
moxial://templates/{id}/fields (the per-template content field reference) — and a
compose-mockup prompt that turns a plain-language brief into the full create → refine → export loop.
Ready to build?
Create an account, grab a token in Settings → API Access, and post your first mockup.
Get startedQuestions? Email support@moxial.com.