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
401Missing, invalid, or revoked token.{"message": "Unauthenticated."}
404Resource not found — including mockups that belong to another workspace.{"message": "…"}
422Validation failed (unknown template, unsupported device, content that violates the template schema…).{"message": "…", "errors": {"field": ["…"]}}
429Rate 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
idstringTemplate id — pass it as template_id to POST /mockups.
platformstringx or instagram.
viewstringThe rendered surface: single (one post) or timeline (a scrollable feed).
archetypestringThe content shape: post (fields at the content root) or timeline (a posts array). See the content payload.
yearintThe era the UI reproduces (fonts, chrome, badges of that year).
device_idsstring[]Devices the template can render in — valid values for device_value.
color_schemes / localesstring[]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_idstring, requiredId of an available template (GET /templates).
namestring, optional, ≤ 255Library label. Defaults to the template name.
device_valuestring, optionalDevice 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_idstring, optionalId 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.
contentobject, optionalThe post data, validated against the template's field_schema and merged onto the seeded defaults. See the content payload.
presentationobject, optionalFraming 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 author object.
  • timeline (feed views) — the content root holds one posts array; each item carries the same per-post fields, including its own author (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
authorprofileThe post's author — see the X author object. Defaults to a snapshot of your (or the bound) profile.
textstring ≤ 1000The post body. Emoji supported.
mediamedia[] ≤ 4Attached images/videos — see the media object.
posted_atdatetimePublication date shown on the post, e.g. "2025-07-17T09:41".
is_repostboolRenders the "reposted" banner above the post.
is_sponsoredboolMarks the post as an ad.
poll_enabledboolAttach a poll. The poll_* fields below only render when this is true.
poll_show_resultsboolShow vote percentages instead of the voting buttons.
poll_endstringThe countdown label, e.g. "1 day left".
poll_optionslist ≤ 4Array of { "text": string, "votes_count": int }.
quote_enabledboolEmbed a quoted post. The quote_* fields below only render when this is true.
quote_authorprofileQuoted post's author — identity subset only: name, username, avatar, verification.
quote_textstring ≤ 1000Quoted post's body.
quote_mediamedia[] ≤ 4Quoted post's media.
quote_posted_atdatetimeQuoted post's date.
likes_count, reposts_count, comments_count, views_count, saves_countintEngagement counters (rendered with K/M abbreviation like the real UI).
viewer_liked, viewer_reposted, viewer_savedboolRender the action buttons in their active (colored) state.
reply_field, reply_field_focused, replybool / bool / string ≤ 1000X Single only — show the reply composer under the post, optionally focused, with a drafted reply.

X — the author object

Field Type Description
name / usernamestringDisplay name and handle (without the @).
avatarstring (URL)Avatar image URL.
verificationenumnone · blue (Verified) · gov (Government, grey) · org (Organization, gold).
affiliation_logostring (URL)Small affiliated-organization logo shown next to the badge (verified accounts only).
is_protected, viewer_follows, viewer_subscribed, is_liveboolIdentity chrome: padlock, "Following" state, subscriber badge, live ring.
profile_idstring | nullProvenance 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
authorprofilename, username, avatar (URL), and is_verified (bool — the blue badge).
mediamedia[] ≤ 10The post's image(s)/video(s); several items render as a carousel.
textstring ≤ 2200The caption. Emoji and hashtags supported.
posted_atdatetimePublication date.
likes_count / comments_countintEngagement counters.
viewer_liked / viewer_savedboolRender 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
textstring | nullMax length from the field's max (e.g. 1000 for an X post).
intinteger | nullCounters (likes, votes…).
boolbooleanToggles (liked, verified, poll_enabled…).
enumstring | nullMust be one of the field's options (e.g. verification).
datetimestring | nullAny parseable date; the editor uses YYYY-MM-DDTHH:mm.
media[]array of media objectsMax items from the field's max. See the media object.
listarray of objectsEach item is validated against the list's item fields (e.g. posts, poll_options).
profileobject | nullAn 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
typeenum, requiredimage · video · gif.
urlstring, requiredPublicly reachable media URL. Private/internal hosts are blanked at export time.
aspectstring, optionalAspect-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_schemeenumlight or dark (must be in the template's color_schemes).
localestringInterface language of the platform chrome (from the template's locales, e.g. en, fr).
device_settingsobjectStatus-bar values: hour (string), battery (string), wifi (0–3), cellular (0–4), location, focus_mode.
interfaceobjectPlatform chrome: notifications_count (int|null), dms_count (int|null), show_home_dot (bool), show_profile_dot (bool).
exportobjectExport 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
idThe mockup id — use it with GET /api/v1/mockups/{id}, or open /mockups/{id}/edit in the app.
templateThe rendering reference: platform, view, archetype, era (year), renderer component, and the field_schema the content was validated against.
deviceThe device frame: value, family (ios/web), and bezel geometry (frame).
system / system_slugThe resolved OS whose status_bar drives era-correct chrome — null for the web/desktop frame.
presentationThe resolved presentation object (your overrides merged over the defaults).
viewerFrozen — the interface identity shown in the platform chrome (e.g. the X timeline header avatar).
contentFrozen — the full post/timeline payload (your overrides merged over the seeded defaults).
viewer_profile_idProvenance: 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-templatesTemplate discovery — same payload as GET /templates.
get-templateOne template with its field_schema — same payload as GET /templates/{id}.
list-profilesYour saved author/viewer identities — pass an id as viewer_profile_id.
create-mockupSame arguments as POST /mockups; returns the mockup document plus an edit_url to open it in the editor.
update-mockupIterate on an existing mockup — partial content / presentation merges, device switch, rename.
list-mockupsYour mockups, newest first (workspace-scoped), each with its edit_url.
get-mockupRetrieve one of your mockups (workspace-scoped), plus its edit_url.
export-mockupRender 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 started

Questions? Email support@moxial.com.