Account, team & billingSOLO PLAN

The MeshMarkup API and webhooks

Read and write your models, notes and review links over the MeshMarkup REST API, subscribe to events, and receive signed webhooks when feedback lands. Endpoints, auth and examples.

6 min readUpdated 19 October 2026

The MeshMarkup API lets your own tools read your studio's models and notes, make review links, reply and change a note's status, and webhooks tell them the moment something happens. Both come with Solo and every plan above it. You create a key under Settings → Integrations → API and webhooks, send it as Authorization: Bearer mm_live_…, and call the endpoints under /api/v1. Webhooks POST a JSON event to your https endpoint, signed with an HMAC-SHA256 header you can verify.

Zapier and Make use the same key and the same endpoints; see Integrations. The reference is also in the app, at the bottom of the API keys page, and you can read it on any plan.

Create an API key

Name the key after what will use it and click Create key. Tick Read only for a key that should never change anything; leave it unticked for anything that makes links, replies or changes statuses.

  1. Open Settings → API

    Click your name at the bottom of the sidebar, then the API tab. You need to be the owner or an admin to create keys.

  2. Name the key and click Create key

    Type what it's for (for example Blender add-on or Studio dashboard) and click Create key.

  3. Copy the key now

    The full key is shown once, under Your new API key, with a Copy button. MeshMarkup stores only a hash of it, so it can't be shown again. If you lose it, create a new one.

Keys are listed with their name, the first few characters and when each was last used, so you can tell them apart and spot one nobody uses. Revoke stops a key working immediately; it stays in the list marked revoked.

A key belongs to the studio. Anything it writes (a reply, a review link) carries the name of the person who created it, so make keys from an account whose name you're happy for clients to see. If that person leaves the studio, the key can still read but can no longer write; make a new one.

Endpoints

Every endpoint lives under https://meshmarkup.com/api/v1/…, with the key in the Authorization header. Bodies are JSON.

EndpointDoesOptions
GET /meThe studio the key belongs to
GET /projectsYour projects
GET /modelsYour models, newest firstlimit (default 50, up to 200), offset, project (a project id), archived=true to include archived models
GET /models/:idOne model, with its versions
GET /models/:id/notesEvery note on the model, with its repliesversion (a version number), includeInternal=true
POST /models/:id/linksA new review linkperms (comment, view or approve), label, version, expiresInDays, hideModel
GET /notes/:idOne note
PATCH /notes/:idChange its statusstatus: open, in_progress, resolved or wont_fix
POST /notes/:id/repliesReply on a notebody, visibility (client or internal)
GET /events?event=…Recent events, shaped exactly like webhook deliverieslimit (up to 50)
POST /hooksSubscribe a URL to an event (REST hooks)url, event or events, source
DELETE /hooks/:idUnsubscribe

The four that change something (links, PATCH /notes, replies, and hooks) need a key that isn't read-only, except hooks, which any key may use.

List your models:

curl -H "Authorization: Bearer mm_live_..." \
     "https://meshmarkup.com/api/v1/models?limit=20"

Each model comes back with its id, name, project (id and name), latestVersion, the number of versions, openNotes, archived and createdAt. The list also returns limit, offset and hasMore, so you can page through with offset.

Get the notes on version 3 of a model:

curl -H "Authorization: Bearer mm_live_..." \
     "https://meshmarkup.com/api/v1/models/MODEL_ID/notes?version=3"

Each note has its number, version, status, part, position, author, createdAt, carriedFromVersion (if it moved across from an earlier version) and a thread of replies. Each reply has its body, author, createdAt, visibility and fromStudio.

Errors

Errors come back as JSON with an error message. A missing, invalid or revoked key returns 401; a studio on Free returns 402; a read-only key trying to write returns 403; a model or note that doesn't exist, or belongs to another studio, returns 404.

Make a review link on a model's latest version:

curl -X POST -H "Authorization: Bearer mm_live_..." \
     -H "content-type: application/json" \
     -d '{"perms":"comment","label":"Acme"}' \
     "https://meshmarkup.com/api/v1/models/MODEL_ID/links"

It answers with the link's url. The link joins the review round already open on that version, so the client's notes stay in one place, just like a link from the Share for review button. A sign-off link (approve) needs Solo or above.

Resolve a note when the matching task is done elsewhere:

curl -X PATCH -H "Authorization: Bearer mm_live_..." \
     -H "content-type: application/json" \
     -d '{"status":"resolved"}' \
     "https://meshmarkup.com/api/v1/notes/NOTE_ID"

Add a webhook

A webhook is an https address that MeshMarkup POSTs to when something happens in your studio.

  1. Enter your endpoint

    Under Webhooks on the API keys page, type your https URL and click Add webhook. Plain http:// addresses are refused.

  2. Copy the signing secret

    A secret starting whsec_ is shown once, under Signing secret for this webhook. Store it with your receiver; you'll need it to check signatures.

  3. Watch its status

    Each webhook is listed with the events it receives and a status: new until the first delivery, ok once one succeeds, and failing with the last error if your endpoint didn't accept one. Remove deletes it.

A webhook added on that page receives three events: pin.created (a new note), comment.created (a reply) and review.approved (a client signed off a version). Through POST /hooks you can also subscribe to pin.resolved (a note was resolved), version.uploaded (a new version) and link.opened (a client opened a review link for the first time). Subscriptions made through POST /hooks are listed there too, marked with their source; revoking the key that made them removes them.

What a delivery looks like

Each event is delivered on its own, oldest first, as a JSON POST with these headers:

  • x-meshmarkup-event: the event name, such as pin.created
  • x-meshmarkup-delivery: a unique id for this delivery
  • x-meshmarkup-signature: sha256= followed by an HMAC-SHA256 of the body, in hex

The body carries the delivery id, the event, occurredAt, the actor (the name of whoever did it), orgId, versionId, pinId, the model (id and name), the version number, modelUrl, and for note events the note (id, number, part, status, surface), its text and a noteUrl that opens the model on that note. Internal replies are never sent out. A link.opened event carries the link (id and label) and the name the client typed, never their email address.

Deliveries are sent in batches rather than instantly. Reply with any 2xx status to accept one; anything else, or no answer within 10 seconds, counts as a failure and is retried on later runs, up to five attempts.

Verify the signature

Always check x-meshmarkup-signature before trusting a delivery. Anyone who learns your endpoint's URL can post to it; only MeshMarkup knows the secret. Compute the HMAC over the raw body, exactly as received, before parsing it as JSON:

const crypto = require("node:crypto");

function verify(rawBody, header, secret) {
  const expected = "sha256=" + crypto
    .createHmac("sha256", secret)
    .update(rawBody)
    .digest("hex");
  const a = Buffer.from(expected);
  const b = Buffer.from(header || "");
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

Parsing the JSON first and re-serialising it can change the bytes, and the signature won't match. Most web frameworks have a way to read the raw body for exactly this reason.

Good to know

  • Scope. Keys and webhooks belong to the whole studio. An admin or the owner can create and revoke them; members can see the list but not change it.
  • Uploading. There's no endpoint for uploading a version yet; use the app or the Blender add-on.
  • Not the same as chat. Webhooks send one event per request for machines to process. For a batched, human-readable summary in a channel, connect Slack or another chat app.

Questions, or need an endpoint that isn't here? Email hello@meshmarkup.com.

Was this guide helpful?

Get feedback on the model, not on a screenshot

Upload a model, send one link, and your client clicks the exact spot. Free to start — no card.

The MeshMarkup API and webhooks — MeshMarkup Help